﻿/*
IchiaTester 共用化上位機
----------------------------------------------------------------------
Version: 2.0.1:
- [新增] menu新增檔案選單，可匯入測試項目、測試結果、使用者資訊
Version: 2.1.0:
- [新增] 新增App日誌，紀錄上位機運行內容(同時包含執行測試的日誌內容)
- [新增] 新增App日誌路徑設定
- [新增] 新增Test Case設定檔記憶功能，匯入檔案後會將當前路徑存入ini檔案
- [修改] 串口重新設定時不需重啟應用程式
Version: 2.1.1:
- [移除] 移除匯入Test result、User Info功能(隱藏UI，保留程式碼)
- [修改] 修正App log路徑更改後log移動問題
Version: 2.1.2:
- [優化] User Info、登入驗證 密碼部分改成SHA256加密
- [優化] User Info 讀寫以及設置流程優化
Version: 2.1.3:
- [修正] 修改了loadingForm遇到例外狀況會關閉問題
Version 2.2.0:
- [新增] 新增串口數據可視化功能
- [新增] 自訂串口數據可視化視窗
- [新增] 新增測項多筆數據平均值
- [新增] 新增測項勾選可跳過
- [新增] 新增測試結束後可以傳送自訂封包內容
- [修改] 串口設定內容
Version 2.2.1:
- [新增] 新增平均值計算方法選取
- [修改] 測項分為測試前、測試中、測試後三個群組
Version 2.2.2:
- [修正] 修正匯入功能相關問題
Version 2.2.3:
- [新增] 新增測項一鍵勾選
- [修改] 波形圖讀取資料邏輯
- [修改] 測項設置檔

Version 2.2.4:
- [新增] 新增3種測試啟動方式: 1.手動啟動 2.掃碼啟動 3.自動啟動。並新增對應設定。
- [優化] 優化並改善系統設置介面。方便使用者設定。
- [修改] ini檔
- [修改] 針對測項格式，修改串口通訊。

----------------------------------------------------------------------
*/
//Using

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using IchiaTester.Utils;
using Timer = System.Timers.Timer;

namespace IchiaTester
{
    public partial class MainForm : Form
    {
        #region DLL_IMPORT

        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal,
            int size, string filePath);

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();


        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        [DllImport("user32.dll")]
        private static extern int SetCursorPos(int x, int y);

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        #endregion

        #region ENUM

        public enum CaseType
        {
            Voltage = 0,
            Current,
            GPIO,
            SPI,
            I2C,
            UART,
            CAN,
            LIN,
            RS232,
            RS485,
            Custom,
            DeviceCMD,
            DeviceCMD_Pass,
            DeviceCMD_Fail,
        }

        public enum AvgType
        {
            Default = 0,
            Median,
            IQR,
            Deviation,
        }

        /// <summary>
        /// 通讯协议类型
        /// </summary>
        public enum CaseCommuType
        {
            None = -1,
            Default = 0,
            Modbus,
            NoRxTx,
        }

        public enum txrxSetting
        {
            NoTxRx = 0,
            DefaultTxRx = 1,
            CustomTxRx = 2,
        }

        private enum CaseTestParamType
        {
            Float = 0,
            LogicLevel,
            CommuCheck,
            AvgCheck,
        }

        /// <summary>
        /// 测试任务流程控制
        /// </summary>
        public enum TestTask
        {
            Idle = 0, // 闲置
            RunPreSetting, // run之前设定
            Device_CMD, // 处理测试是否需要供电 
            Preprocess, // 测试预处理(刷新ListView以及传送指令到周边设备)
            Process, // 處理周邊設備回傳數據並判斷 处理周边设备回传数据并判断
            FailProcess, // 处理测试失败
            KeepTest, // 保持测试
            DisplayResult, // 展示结果
            RunPostSetting,
            CreateLog, // 生成测试结果log日志
            EmergencyStop,
            SerialPortError,
            TestEnd,
            FinishProcess,
        }

        /// <summary>
        /// 当前系统状态
        /// </summary>
        public enum SystemState
        {
            Initiliazing = 0,
            Idle,
            AppSetting,
            AppSetSuccess,
            AppSetFinish,
            UISetting,
            UISetSuccess,
            FileLoading,
            LoadFileSuccess,
            LoadFileFinished,
            GetValue,
            GetValueSuccess,
            SetValue,
            SetValueSuccess,
            FileSaving,
            SaveFileSuccess,
            SetPorts,
            SetPortsSuccess,
            TestTaskInitial,
            TestTaskExecute,
            TestTaskFinish,
            Communicate,
            CommunicateSuccess,
            WarningHandling,
            ErrorHandling,
            ShuttingDown,
            LogEnd,
        }

        #endregion

        #region STRUCT 波形图配置信息结构体

        public struct VisualizerConfig
        {
            public string Title; // 标题名称
            public Color WaveColor; // 颜色
            public int UpLimit; // 上限值
            public Color UpLimitColor; // 上限值颜色
            public int LowLimit; // 下限值
            public Color LowLimitColor; // 下限值颜色
            public int PortID; // 目标串口
            public string FrameHead; // 报文头
            public string ValuePos; // 数据位置
            public string XAxisName; // X轴名称
            public string YAxisName; // Y轴名称
            public int QueueSize; // 缓存大小
        }

        #endregion

        #region CONST

        //const
        public const int MOUSEEVENTF_LEFTDOWN = 0x02;
        public const int MOUSEEVENTF_LEFTUP = 0x04;

        public const bool DEBUG_STATE_LOG = false;
        public const float VOLTAGE_Ref = 100.0f;
        public const int TEST_END_TO_IDLE_TIME = 1500;
        public const int RESEND_DELAY = 10; //Delay between each command resend.(ms)

        #endregion

        #region VARIABLE

        //Variable

        //For Application Log
        private static LogContent logger;
        public static string InitialTime = DateTime.Now.ToString("yyyyMMddHHmmss");
        private DateTime AppStartTime;


        private GroupBoxPaint groupboxPaint = new GroupBoxPaint();

        //For test result 
        public int passNum = 0; // pass数量
        public int failNum = 0; // fail数量
        public int totalNum = 0; // 总数
        public float yeild = 0.0f; // 良率
        private int finalResult = -1; //false = Fail, true = Pass 
        private const int PASS = 1;
        private const int FAIL = 0;
        private byte[] TestResultData;

        //System value
        private bool isTesting = false;
        private Thread threadTest;
        private Process ThisProcess;
        LoadingForm loadingForm; // 加载窗口
        public static int TestCaseID;
        public static int PreTestCaseID;

        public static int PostTestCaseID;

        //public static int CaseID;
        private int NotifyDeviceIndex = 0;
        private const int NotifyPLCDelay = 500;
        private bool IsTestData = false;
        private DataManager dM;
        private ChecksumCalculator checksumCalculator;
        private bool loadingFormOpen = false;


        //For serial
        private byte[] tempUartTx = new byte[8];
        private byte[] tempModbusTx = new byte[8];

        /// <summary>
        /// 内存中的SerialPort字典类
        /// </summary>
        public Dictionary<string, bool> serialBufferReady = new Dictionary<string, bool>();

        public SerialPort serialport;
        private readonly object lockObject = new object();
        private byte[] _ReceivedData;

        private byte[] ReceivedData
        {
            get
            {
                lock (lockObject)
                {
                    return _ReceivedData;
                }
            }
            set
            {
                lock (lockObject)
                {
                    _ReceivedData = value;
                }
            }
        }
        private static byte[] SendUART = new byte[UartSetting.UART_TX_FRAME_NUM];
        
        /// <summary>
        /// 存Modbus报文的字节数组,长度为8位
        /// </summary>
        private static byte[] SendModbus = new byte[ModbusSetting.MODBUS_TX_FRAME_LEN];

        private static byte[] SendCustom = new byte[CustomSetting.CUSTOM_FRAME_NUM];

        //public static List<byte[]> SerialRxBuffer = new List<byte[]>();
        public static Dictionary<string, List<byte>> spRxBuffer = new Dictionary<string, List<byte>>();
        private string[] notifyCMDs;
        private byte[] notifyFrame = new byte[8];
        public static int SP_ReadCounter = 0;
        public static int PortID = 0;
        private bool hasSync = false;
        private bool isSynchronized = false;
        private int syncIndex = 0;


        //For visualizer
        public VisualizerConfig tempVisualizerConfig { get; set; } = new VisualizerConfig();


        //For test initial method
        public static bool AllowTestStart = false; // 允许测试开始的标志
        public static bool TestStartCMDAsked = false;
        Thread threadAskDevice;
        private volatile bool KillThreadAskDevice = false;


        //For test parameter

        public int[] Retry_Counter;
        private int Total_Retry_Count = 0;
        //public decimal barcodeNumber = 0;
        public int delayToStart;
        private TestTask testTask = TestTask.Idle;
        private bool IsPass = false;
        private bool IsNotResponse = false;
        private bool IsModbus = false;
        private string ResultValue;
        List<double> AvgCheckValue = new List<double>();
        private int AvgCheckLimiter = 0;
        private int limiterIndex = 0;
        private const double LimiterRatio = 2.5;


        //For Timer
        private static Timer countdownTimer; // 倒计时Timer
        private static int countdownTime; // 倒计时时间
        private static bool isPaused = false; // Timer暂停标志
        private static bool isTimeout = false; // Timer超时标志
        private static int timeoutInterval = 100; //时间间隔 time base 100ms
        private static int resendCount = 0; // 重发次数
        public static MainForm Instance { get; private set; }

        //For resize
        //Size
        private Size formInitialSize;
        private Size groupBox1InitialSize;
        private Size groupBox2InitialSize;
        private Size groupBox3InitialSize;
        private Size groupBox4InitialSize;
        private Size tbLogInitialSize;
        private Size tbMsgInitialSize;
        private Size tbResultInitialSize;
        private Size lvTestInitialSize;
        private Size btnStartInitialSize;
        private Size btnClearInitialSize;
        private Size picLogoInitialSize;

        private float btnStartFontInitialSize;
        private float btnClearFontInitialSize;

        private float tbResultFontInitailSize;

        //location
        private Point groupBoxStartButtonLocation;
        private Point groupBoxResultDisplayLocation;
        private Point groupBox3Location;
        private Point groupBox4Location;
        private Point tbLogLocation;
        private Point lbSnLocation;
        private Point tbBarcodeLocation;
        private Point tbPassLocation;
        private Point tbFailLocation;
        private Point tbTotalLocation;
        private Point tbPercentageLocation;
        private Point btnCLearLocation;
        private Point picLogoLocation;

        #endregion

        #region Visualizer 波形图

        WaveFormBuilder.WaveFormBuilder waveFormBuilder = new WaveFormBuilder.WaveFormBuilder();

        /// <summary>
        /// 波形图配置字典
        /// </summary>
        public Dictionary<string, VisualizerConfig> visualizerConfigDict = new Dictionary<string, VisualizerConfig>();

        /// <summary>
        /// 波形图字典
        /// </summary>
        public Dictionary<string, Form> visualizerForms = new Dictionary<string, Form>();

        public Dictionary<string, BlockingCollection<double>> visualizerBuffers =
            new Dictionary<string, BlockingCollection<double>>();

        public Thread threadVisualizer;
        private volatile bool updateVisualizer = true;
        public static bool PauseUpdateVisualizer = false;
        private const int InvalideValue = -1;
        private const int AxisXMinimum = 0;
        private const int AxisXMaximum = 100; //**還沒添加自定義功能**

        private const int AxisYMinimum = -10;
        /*
        Form portWatcherForm = new Form();
        private void addPortWatcher_Click(object sender, EventArgs e)
        {
            //新增視窗大小為w=400 h=700，顯示串口數據，視窗內含有一個scrollable 的textbox
            InfoLogAppend("Open port watcher ......", SystemState.AppSetting);
            portWatcherForm = new Form
            {
                Text = "Port Watcher",
                Size = new Size(400, 700),
                StartPosition = FormStartPosition.CenterScreen,
                AutoScroll = true,
            };
            TextBox tbDataShow = new TextBox();
            tbDataShow.Multiline = true;
            tbDataShow.ScrollBars = ScrollBars.Vertical;
            tbDataShow.Dock = DockStyle.Fill;
            portWatcherForm.Controls.Add(tbDataShow);
            portWatcherForm.Show();
        }
        private void UpdatePortWatcher(string portName, string data)
        {
            try
            {
                if (portWatcherForm == null || portWatcherForm.IsDisposed)
                {
                    return;
                }
                if (portName == dM.serialPorts[1].PortName)
                {
                    string dataToShow = string.Format("{0}: {1}", portName, data);
                    //更新port watcher視窗內容    
                    portWatcherForm.Invoke(new MethodInvoker(() =>
                    {
                        TextBox tbDataShow = (TextBox)portWatcherForm.Controls[0];
                        tbDataShow.AppendText(dataToShow + Environment.NewLine);
                    }));
                }
            }
            catch (Exception ex)
            {
                string dataToShow = string.Format("{0}: {1}", portName, data);
                ErrorLogAppend($"Fail to update port watcher with: {dataToShow}", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
            }

        }
        */

        /// <summary>
        /// 添加波形图
        /// 工具 -> Visaulizer -> Add Wave Chart
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addVisaulizer_Click(object sender, EventArgs e)
        {
            InfoLogAppend("Open visualizer ......", SystemState.AppSetting);
            PauseUpdateVisualizer = true;

            // 创建波形图参数设置窗口
            VisualizerForm visualizerForm = new VisualizerForm(this);
            if (visualizerForm.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    //Check if the visualizer config is already exist
                    if (visualizerConfigDict.ContainsKey(tempVisualizerConfig.Title))
                    {
                        WarnLogAppend($"Visualizer {tempVisualizerConfig.Title} already exist!",
                            SystemState.WarningHandling);
                        ShowWarnMsg($"{tempVisualizerConfig.Title} 已經存在，請重新設定!");
                        return;
                    }

                    visualizerConfigDict.Add(tempVisualizerConfig.Title, tempVisualizerConfig);
                    visualizerBuffers.Add(tempVisualizerConfig.Title, new BlockingCollection<double>());
                    // 将 Form 和 Chart 添加到字典

                    ShowChartForm(tempVisualizerConfig.Title);
                    AppendVSConfig(tempVisualizerConfig);
                    RefreshMenueVisualizerOpenRemove();
                    Thread.Sleep(100);
                    if (threadVisualizer == null && !threadVisualizer.IsAlive)
                    {
                        updateVisualizer = true;
                        threadVisualizer = new Thread(UpdateWave);
                        threadVisualizer.Start();
                    }
                }
                catch (Exception ex)
                {
                    //visualizerConfigDict.Remove(tempVisualizerConfig.Title);
                    //visualizerBuffers.Remove(tempVisualizerConfig.Title);
                    ErrorLogAppend($"Fail to setup visualizer: {tempVisualizerConfig.Title}",
                        ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                    ShowErrorMsg($"Visualizer:  {tempVisualizerConfig.Title} 設定失敗! - {ex.Message}");
                }
            }

            PauseUpdateVisualizer = false;
        }

        /// <summary>
        /// 添加波形图配置文件,在当前app的Startup路径下生成对应文件,生成对应的配置文件
        /// </summary>
        /// <param name="config"></param>
        private void AppendVSConfig(VisualizerConfig config)
        {
            string configPath = Path.Combine(Application.StartupPath, SystemSetting.VISUALIZER_CFG_FILENAME);
            InfoLogAppend($"Try to append visualizer config: {config.Title} to {configPath}", SystemState.FileSaving);
            try
            {
                string line = string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11}",
                    config.Title,
                    config.WaveColor.Name,
                    config.UpLimit,
                    config.UpLimitColor.Name,
                    config.LowLimit,
                    config.LowLimitColor.Name,
                    config.PortID,
                    config.FrameHead,
                    config.ValuePos,
                    config.XAxisName,
                    config.YAxisName,
                    config.QueueSize);
                File.AppendAllText(configPath, line + Environment.NewLine);
                InfoLogAppend($"Successfully append visualizer config: {config.Title} to {configPath}",
                    SystemState.FileSaving);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to append visualizer config: {config.Title}", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 加载Visualizer配置文件
        /// </summary>
        /// <param name="filepath">文件路径</param>
        private void LoadVisualizerCFG(string filepath)
        {
            InfoLogAppend($"Try to load visualizer config from {filepath}", SystemState.FileLoading);
            try
            {
                // 判断文件是否存在
                if (!File.Exists(filepath))
                {
                    WarnLogAppend($"File {filepath} does not exist.", SystemState.WarningHandling);
                }

                // 逐行读取多个配置文件的内容
                string[] lines = File.ReadAllLines(filepath);
                // 遍历添加到visualizerConfigDict字典中
                foreach (var line in lines)
                {
                    if (!string.IsNullOrEmpty(line))
                    {
                        var parts = line.Split(',');
                        var vsconfig = new VisualizerConfig();
                        vsconfig.Title = parts[0].Trim();
                        vsconfig.WaveColor = (Color)typeof(Color).GetProperty(parts[1].Trim()).GetValue(null, null);
                        vsconfig.UpLimit = int.Parse(parts[2].Trim());
                        vsconfig.UpLimitColor = (Color)typeof(Color).GetProperty(parts[3].Trim()).GetValue(null, null);
                        vsconfig.LowLimit = int.Parse(parts[4].Trim());
                        vsconfig.LowLimitColor = (Color)typeof(Color).GetProperty(parts[5].Trim()).GetValue(null, null);
                        vsconfig.PortID = int.Parse(parts[6].Trim());
                        vsconfig.FrameHead = parts[7].Trim();
                        vsconfig.ValuePos = parts[8].Trim();
                        vsconfig.XAxisName = parts[9].Trim();
                        vsconfig.YAxisName = parts[10].Trim();
                        vsconfig.QueueSize = int.Parse(parts[11].Trim());
                        visualizerConfigDict.Add(vsconfig.Title, vsconfig);
                        visualizerBuffers.Add(vsconfig.Title, new BlockingCollection<double>());
                        ShowChartForm(vsconfig.Title);
                    }
                }

                if (threadVisualizer == null || !threadVisualizer.IsAlive)
                {
                    updateVisualizer = true;
                    threadVisualizer = new Thread(UpdateWave);
                    threadVisualizer.Start();
                }

                RefreshMenueVisualizerOpenRemove();
                InfoLogAppend($"Successfully load visualizer config from {filepath}", SystemState.LoadFileSuccess);
                PauseUpdateVisualizer = false;
            }
            catch (FileNotFoundException ex)
            {
                ErrorLogAppend($"Visualizer config file not found: {filepath}", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 删除Visualizer配置文件
        /// 1.查找是否存在,存在先关闭窗体释放资源
        /// 2.删除文件,重写文件
        /// </summary>
        /// <param name="title"></param>
        private void RemoveVisualizer(string title)
        {
            InfoLogAppend($"Try to remove visualizer {title}......", SystemState.UISetting);
            try
            {
                PauseUpdateVisualizer = true;
                Thread.Sleep(100);
                if (visualizerForms.ContainsKey(title) && IsFormOpen(visualizerForms[title]))
                {
                    //visualizerFormsOpen[title] = false;
                    visualizerForms[title].Dispose();
                    visualizerForms[title].Close();
                }

                //visualizerFormsOpen.Remove(title);
                visualizerForms.Remove(title);
                visualizerBuffers.Remove(title);
                visualizerConfigDict.Remove(title);
                //Delete title from config file
                string configPath = Path.Combine(Application.StartupPath, SystemSetting.VISUALIZER_CFG_FILENAME);
                string[] lines = File.ReadAllLines(configPath);
                File.Delete(configPath);
                foreach (var line in lines)
                {
                    var parts = line.Split(',');
                    if (parts[0].Trim() != title)
                    {
                        File.AppendAllText(configPath, line + Environment.NewLine);
                    }
                }

                RefreshMenueVisualizerOpenRemove();
                InfoLogAppend($"Visualizer {title} removed!", SystemState.UISetSuccess);
                PauseUpdateVisualizer = false;
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to remove visualizer {title}", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg($"Fail to remove visualizer: {title}");
            }
        }

        /// <summary>
        /// 刷新Visualizer
        /// </summary>
        private void RefreshMenueVisualizerOpenRemove()
        {
            InfoLogAppend($"Try to refresh menue visualizer open and remove......", SystemState.UISetting);
            openVisaulizer.DropDownItems.Clear();
            removeVisaulizer.DropDownItems.Clear();
            foreach (KeyValuePair<string, VisualizerConfig> configPair in visualizerConfigDict)
            {
                string title = configPair.Key;
                ToolStripMenuItem menuOpen = new ToolStripMenuItem(title);
                ToolStripMenuItem menuRemove = new ToolStripMenuItem(title);
                menuOpen.Click += (s, e) => ShowChartForm(title);
                menuRemove.Click += (s, e) => RemoveVisualizer(title);
                openVisaulizer.DropDownItems.Add(menuOpen);
                removeVisaulizer.DropDownItems.Add(menuRemove);
            }

            InfoLogAppend($"Menue visualizer open and remove refreshed!", SystemState.UISetSuccess);
        }

        /// <summary>
        /// 根据title去 visualizerConfig 字典中查找对应配置文件
        /// 根据封装好的构造器,展示图表窗体
        /// </summary>
        /// <param name="title"></param>
        /// <returns></returns>
        public Form CreateNewWave(string title)
        {
            VisualizerConfig config = visualizerConfigDict[title];
            return waveFormBuilder.ShowChartForm(title, config.XAxisName, config.YAxisName, "Average", config.WaveColor,
                ChartDashStyle.Solid, SeriesChartType.Line);
        }

        /// <summary>
        /// 判断窗体是否打开
        /// </summary>
        /// <param name="chartForm"></param>
        /// <returns></returns>
        public bool IsFormOpen(Form chartForm)
        {
            return chartForm != null && !chartForm.IsDisposed && chartForm.IsHandleCreated;
        }

        /// <summary>
        /// 展示图表窗体
        /// </summary>
        /// <param name="title"></param>
        public void ShowChartForm(string title)
        {
            InfoLogAppend($"Try to show chart form: {title}......", SystemState.UISetting);
            try
            {
                if (visualizerConfigDict.ContainsKey(title))
                {
                    Form form = CreateNewWave(title);
                    visualizerForms.Add(title, form);
                    visualizerForms[title].FormClosing += (s, args) => OnChartFormClosed(title);
                    //Add up and low limit line
                    waveFormBuilder.AddWaveSeries(form, "UpperLimit", visualizerConfigDict[title].UpLimitColor,
                        ChartDashStyle.Dash, SeriesChartType.Line);
                    waveFormBuilder.AddWaveSeries(form, "LowerLimit", visualizerConfigDict[title].LowLimitColor,
                        ChartDashStyle.Dash, SeriesChartType.Line);
                    //Add 0 line
                    waveFormBuilder.AddWaveSeries(form, "0", Color.Gray, ChartDashStyle.Dash, SeriesChartType.Line);
                    //Set X and Y axis range
                    waveFormBuilder.SetAxisMinMax(form, AxisXMinimum, AxisXMaximum, AxisYMinimum,
                        visualizerConfigDict[title].UpLimit * 1.2);
                    //Set wave initial value for upper limit, lower limit, and average
                    double[] xData = new double[AxisXMaximum];
                    double[] yData = new double[AxisXMaximum];
                    //Set average wave
                    xData = Enumerable.Range(0, AxisXMaximum).Select(i => (double)i).ToArray();
                    yData = Enumerable.Repeat(0.0, AxisXMaximum).ToArray();
                    waveFormBuilder.SetWaveInitialData(form, "Average", xData, yData);
                    //Set upper limit wave
                    yData = Enumerable.Repeat((double)visualizerConfigDict[title].UpLimit, AxisXMaximum).ToArray();
                    waveFormBuilder.SetWaveInitialData(form, "UpperLimit", xData, yData);
                    //Set lower limit wave
                    yData = Enumerable.Repeat((double)visualizerConfigDict[title].LowLimit, AxisXMaximum).ToArray();
                    waveFormBuilder.SetWaveInitialData(form, "LowerLimit", xData, yData);
                    //Set wave at 0
                    yData = Enumerable.Repeat(0.0, AxisXMaximum).ToArray();
                    waveFormBuilder.SetWaveInitialData(form, "0", xData, yData);

                    form.Show();
                    form.BringToFront();
                    InfoLogAppend($"Chart form {title} showed!", SystemState.UISetSuccess);
                    //visualizerFormsOpen.Add(title, true);
                }
                else
                {
                    WarnLogAppend($"Visualizer {title} not exist!", SystemState.WarningHandling);
                    ShowWarnMsg($"{title} 不存在，請重新設定!");
                }
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to show chart form: {title}", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg($"Fail to show chart form: {title}");
            }
        }

        /// <summary>
        /// 更新波形
        /// </summary>
        public void UpdateWave()
        {
            while (updateVisualizer)
            {
                try
                {
                    if (PauseUpdateVisualizer)
                    {
                        foreach (var bf in visualizerBuffers.Values)
                        {
                            bf.CompleteAdding();
                        }
                    }

                    if (visualizerBuffers.Count > 0 && !PauseUpdateVisualizer)
                    {
                        foreach (var title in visualizerBuffers.Keys)
                        {
                            if (!updateVisualizer)
                            {
                                break;
                            }

                            //Kill thread
                            BlockingCollection<double> buffer = visualizerBuffers[title];
                            if (buffer != null && visualizerForms.ContainsKey(title) &&
                                IsFormOpen(visualizerForms[title]))
                            {
                                if (!updateVisualizer)
                                {
                                    break;
                                } //Kill thread

                                if (buffer.TryTake(out double data, 100))
                                {
                                    visualizerForms[title].Invoke((MethodInvoker)delegate
                                    {
                                        waveFormBuilder.UpdateWaveData(visualizerForms[title], "Average", data);
                                        UpdateYAxisRange(visualizerForms[title], visualizerConfigDict[title].UpLimit);
                                    });
                                }
                            }
                        }
                    }
                }
                catch (InvalidOperationException ex)
                {
                    // 處理 InvalidOperationException 例外狀況，進行下一個數據處理
                    ErrorLogAppend($"InvalidOperationException ", ex.Message + ex.StackTrace,
                        SystemState.WarningHandling);
                }
                catch (Exception ex)
                {
                    ErrorLogAppend($"Fail to update wave: {ex.Message + ex.StackTrace}", ex.StackTrace,
                        SystemState.ErrorHandling);
                    //ShowErrorMsg($"Fail to update wave:  {ex.Message}");
                }
            }
        }

        /// <summary>
        /// 表格关闭触发事件
        /// </summary>
        /// <param name="title"></param>
        public void OnChartFormClosed(string title)
        {
            InfoLogAppend($"Try to close visualizer {title}......", SystemState.UISetting);
            try
            {
                PauseUpdateVisualizer = true;
                Thread.Sleep(100);
                if (visualizerForms.ContainsKey(title))
                {
                    if (!IsFormOpen(visualizerForms[title]))
                    {
                        return;
                    }

                    //visualizerFormsOpen[title] = false;
                    visualizerForms[title].Dispose();
                    visualizerForms.Remove(title);
                    visualizerBuffers.Remove(title);
                }

                Thread.Sleep(100);
                PauseUpdateVisualizer = false;
                InfoLogAppend($"Visualizer {title} closed!", SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to close visualizer: {title}", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg($"Fail to close visualizer: {title}");
            }
        }

        /// <summary>
        /// 获取表格数据
        /// </summary>
        /// <param name="portName">端口号</param>
        /// <param name="received">接收字节数组</param>
        /// <returns></returns>
        private async Task<Dictionary<string, int>> GetChartData(string portName, byte[] received)
        {
            try
            {
                Dictionary<string, int> chartData = new Dictionary<string, int>();
                if (visualizerConfigDict.Count == 0)
                {
                    return null;
                }

                foreach (KeyValuePair<string, VisualizerConfig> configPair in visualizerConfigDict)
                {
                    var pair = configPair;
                    string targetPort = GetPortNameByID(configPair.Value.PortID);
                    byte frameHead = Convert.ToByte(configPair.Value.FrameHead, 16);
                    await Task.Run(() =>
                    {
                        if (targetPort == portName && received[0] == frameHead)
                        {
                            int value = 0;
                            int[] positions = (pair.Value.ValuePos.Split(' ')).Select(int.Parse).ToArray();
                            if (positions.Length == 1)
                            {
                                value = received[positions[0]];
                            }
                            else if (positions.Length == 2)
                            {
                                ushort combinedValue = (ushort)((received[positions[0]] << 8) | received[positions[1]]);
                                value = (short)combinedValue;
                            }
                            else
                            {
                                value = 0;
                                ErrorLogAppend($"Fail to get chart data: {pair.Value.Title}", "Invalid value position",
                                    SystemState.ErrorHandling);
                                ShowErrorMsg($"Visualizer:  {pair.Value.Title} 無效的數據位置!");
                            }

                            //Limit the value to avoid overflow
                            if (value > (pair.Value.UpLimit) * LimiterRatio)
                            {
                                value = (int)(pair.Value.UpLimit * LimiterRatio);
                            }

                            chartData.Add(pair.Value.Title, value);
                        }
                    });
                }

                return chartData;
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to get chart data: {ex.Message + ex.StackTrace}", ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg($"Visualizer fail to get chart data:  {ex.Message}");
                return null;
            }
        }

        private void UpdateYAxisRange(Form form, int upLimit)
        {
            if (form.Tag is Chart waveChart)
            {
                // 根據數據動態調整 Y 軸
                if (waveChart.Series[0].Points.Count > 0)
                {
                    waveChart.BeginInvoke(new MethodInvoker(() =>
                    {
                        var values = waveChart.Series[0].Points.Select(p => p.YValues[0]);
                        var max = Math.Ceiling(values.Max()) + 5; // 最大值留 10 的邊界
                        var min = Math.Ceiling(values.Min()) - 5;

                        waveChart.ChartAreas[0].AxisY.Maximum = (max > upLimit) ? max : upLimit * 1.2;
                        waveChart.ChartAreas[0].AxisY.Minimum = (min < AxisYMinimum) ? min : AxisYMinimum;
                    }));
                }
            }
        }

        #endregion

        #region MENU 打开文件对话框控件
        /// <summary>
        /// 打开文件对话框控件
        /// </summary>
        /// <param name="title"></param>
        /// <param name="filter"></param>
        /// <returns>打开的文件路径+文件名</returns>
        private string OpenSaveDialog(string title, string filter)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Title = title;
            openFileDialog.Filter = filter;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                return openFileDialog.FileName;
            }
            return string.Empty;
        }

        /// <summary>
        /// 点击"工程模式"按钮,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuEngineerMode_Click(object sender, EventArgs e) //Has log
        {
            menuEngineerMode.Checked = !menuEngineerMode.Checked;
            InfoLogAppend($"Engineer mode = {menuEngineerMode.Checked}", SystemState.AppSetSuccess);
            tbBarCode.Focus();
        }

        /// <summary>
        /// 点击"系统设置"按钮,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuSystemConfig_Click(object sender, EventArgs e) //Has log
        {
            InfoLogAppend("Setting system configure ...", SystemState.AppSetting);
            AllowTestStart = false;
            // 创建系统设置窗体
            SystemConfigForm systemConfig = new SystemConfigForm(this, dM);
            if (systemConfig.ShowDialog() == DialogResult.OK)
            {
            }

            AllowTestStart = true;
            InfoLogAppend("Setting system configure finished", SystemState.AppSetFinish);
            tbBarCode.Focus();
        }

        /// <summary>
        /// 点击"用户管理"按钮,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuUserConfig_Click(object sender, EventArgs e)
        {
            AllowTestStart = false;
            // 创建用户管理窗体
            UserForm userForm = new UserForm(this);
            userForm.ShowDialog();
            AllowTestStart = true;
            tbBarCode.Focus();
        }

        /// <summary>
        /// 点击"用户登入",触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuUserLogin_Click(object sender, EventArgs e) //Has log
        {
            try
            {
                AllowTestStart = false; // 用户登录前不允许测试开始
                InfoLogAppend("User login ......", SystemState.AppSetting);
                // 创建登录窗体
                LoginForm loginForm = new LoginForm(this);
                if (loginForm.ShowDialog() == DialogResult.OK)
                {
                    InfoLogAppend($"User: {SystemSetting.loginUserName} login success!", SystemState.AppSetSuccess);
                    AllowTestStart = true; // 用户登录后允许测试开始
                }
                else
                {
                    WarnLogAppend("User login failed!", SystemState.WarningHandling);
                    return;
                }

                InfoLogAppend("User login finished", SystemState.AppSetFinish);
                tbBarCode.Focus(); // 在控件可见且启用时，使用 textbox.Focus() 即可尝试设置焦点
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Fail to login", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg($"登入過程出現異常" + Environment.NewLine + $"{ex.Message}");
            }
        }

        /// <summary>
        /// 点击"串口设置",触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuSerialPortConfig_Click(object sender, EventArgs e) //Has log
        {
            InfoLogAppend("Setting serial port configure ...", SystemState.AppSetting);
            dM.CloseAllSerialPort();
            // 创建 串口设置 窗体
            SerialPortConfigForm serialPortConfigForm = new SerialPortConfigForm(this);
            if (serialPortConfigForm.ShowDialog() == DialogResult.OK)
            {
                bool loadSP = dM.LoadSerialPortConfig(SystemSetting.serialPortConfigPath);
                if (loadSP)
                {
                    ShowInfoMsg("串口已完成重新配置!");
                }
                else
                {
                    ShowWarnMsg("串口重新配置失敗!詳細內容請檢查APP log");
                }
            }

            dM.GetAllSerialPort();
            if (DataManager.IsPortAvailable)
            {
                UpdateTbResult(System_msg.WAIT_STR, Color.LightBlue);
                AllowTestStart = true;
                InfoLogAppend("Setting serial port configure finished", SystemState.AppSetFinish);
            }
            else
            {
                UpdateTbResult(System_msg.CHECK_SERIAL_PORT, Color.LightBlue);
                WarnLogAppend("Serial port not available!", SystemState.WarningHandling);
            }

            tbBarCode.Focus();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuUserLogout_Click(object sender, EventArgs e) //Has log
        {
            AllowTestStart = false;
            InfoLogAppend("User logout ......", SystemState.AppSetting);
            menuEngineerMode.Checked = false;
            menuEngineerMode.Enabled = false;
            menuSystemConfig.Enabled = false;
            menuUserConfig.Enabled = false;
            menuSerialPortConfig.Enabled = false;
            menuUserLogin.Enabled = true;
            SystemSetting.isAdmin = false;
            SystemSetting.isEngineer = false;
            SystemSetting.loginUserName = System_msg.NO_LOGIN_STR;
            groupBoxStartButton.Enabled = false;
            groupBoxResultDisplay.Enabled = false;
            tbResult.Text = System_msg.NEED_LOGIN_STR;
            tbResult.BackColor = Color.LightBlue;
            DisplayWorkerInfo();
            menuUserLogout.Enabled = false;
            WarnLogAppend("User logout!", SystemState.AppSetSuccess);
            InfoLogAppend("User logout finished", SystemState.AppSetFinish);
        }

        /// <summary>
        /// 点击 Start 按钮,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e) //Has log
        {
            //手動執行測試
            InfoLogAppend("Try to  manually start test......", SystemState.TestTaskInitial);
            if (!bool.Parse(SystemSetting.startButtonCheck))
            {
                ShowWarnMsg("手動測試按鈕功能未啟用!請檢查系統設置。");
                WarnLogAppend("Test did not start! Manual test button function is not enabled! ",
                    SystemState.WarningHandling);
                return;
            }

            if (!AllowTestStart)
            {
                ShowWarnMsg("系統尚未設置完畢or未正確配置，請確認所有配置皆正確。");
                WarnLogAppend("Test did not start! System was not correctly setup . ", SystemState.WarningHandling);
                return;
            }

            if (lvTest.Items.Count == 0)
            {
                ShowErrorMsg("測試項目檔未載入!");
                WarnLogAppend("Test did not start! No test case data loaded! ", SystemState.WarningHandling);
                return;
            }

            if (!menuEngineerMode.Checked)
            {
                ShowWarnMsg("非工程模式，請使用其他方式開是測試。或是以Engineer or Admin身分登入系統，並開啟工程模式!");
                WarnLogAppend("Test did not start! Not under engineer mode! ", SystemState.WarningHandling);
                return;
            }

            RunStartTest();
        }

        /// <summary>
        /// 点击 Clear 按钮,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnClear_Click(object sender, EventArgs e) //Has log
        {
            InfoLogAppend("Try to clear test result......", SystemState.UISetting);
            passNum = 0;
            failNum = 0;
            totalNum = 0;
            yeild = 0.0f;

            DisplayResult();

            InfoLogAppend("Successfully clear test result. ", SystemState.UISetSuccess);
            tbBarCode.Focus();
        }

        /// <summary>
        /// 条形码textBox - 扫码枪,扫码后内容发生改变后触发 =>开始测试
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tbBarCode_TextChanged(object sender, EventArgs e)
        {
            InfoLogAppend("Try to start test by barcode......", SystemState.TestTaskInitial);
            try
            {
                if (String.IsNullOrEmpty(SystemSetting.barcodeDetectCheck) ||
                    !bool.Parse(SystemSetting.barcodeDetectCheck))
                {
                    ShowWarnMsg("掃碼啟動功能未啟用!請檢查系統設置。");
                    WarnLogAppend("Test did not start!Barcode detect mode is not enabled! ",
                        SystemState.WarningHandling);
                    return;
                }

                if (String.IsNullOrEmpty(SystemSetting.barcodeLength))
                {
                    ShowWarnMsg("Barcode長度未設定!請檢查系統設置。");
                    WarnLogAppend("Test did not start! Barcode length is not set! ", SystemState.WarningHandling);
                    return;
                }

                //檢查掃碼槍是否發送完畢(結尾資料會送出\r\n兩個字元)
                if (tbBarCode.Text.Contains("\r\n"))
                {
                    InfoLogAppend("Barcode changed, try to start test task......", SystemState.TestTaskInitial);
                    if (menuEngineerMode.Checked)
                    {
                        // 工程师模式
                        string[] splitElemp = tbBarCode.Text.Split('\r');
                        tbBarCode.Text = splitElemp[0];
                        SystemSetting.barCode = splitElemp[0];
                        SystemSetting.BarCodeFilename = ""; //clear barcode filename

                        string[] splitBarcode = splitElemp[0].Split('/');
                        int i;
                        for (i = 0; i < splitBarcode.Length - 1; i++)
                        {
                            SystemSetting.BarCodeFilename += splitBarcode[i] + "-";
                        }

                        SystemSetting.BarCodeFilename += splitBarcode[i];

                        //檢查是否重測?
                        Total_Retry_Count = (SystemSetting.previousBarCode != SystemSetting.barCode)
                            ? Total_Retry_Count++
                            : 0;

                        //啟動測試
                        RunStartTest();
                    }
                    else
                    {
                        bool barcodeExist = int.TryParse(SystemSetting.barcodeLength, out int barcodeLen);
                        if (barcodeExist && tbBarCode.Text.Length == (barcodeLen + 2)) //如果按Enter會多\r\n兩個字元
                        {
                            string[] splitElemp = tbBarCode.Text.Split('\r');
                            tbBarCode.Text = splitElemp[0];
                            SystemSetting.barCode = splitElemp[0];
                            SystemSetting.BarCodeFilename = ""; //clear barcode filename

                            string[] splitBarcode = splitElemp[0].Split('/');
                            int i;
                            for (i = 0; i < splitBarcode.Length - 1; i++)
                            {
                                SystemSetting.BarCodeFilename += splitBarcode[i] + "-";
                            }

                            SystemSetting.BarCodeFilename += splitBarcode[i];

                            //檢查是否重測?
                            Total_Retry_Count = (SystemSetting.previousBarCode == SystemSetting.barCode)
                                ? Total_Retry_Count++
                                : 0;

                            //啟動測試
                            RunStartTest();
                        }
                        else
                        {
                            tbBarCode.Text = "";
                            ShowWarnMsg($"Barcode is wrong!");
                            WarnLogAppend("Test task aborted! Wrong barcode input detected! ",
                                SystemState.WarningHandling);
                            tbBarCode.Focus();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Fail to handle barcode input!", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg("Unexpected error occur while handling barcode input!");
            }
        }

        #endregion

        #region MENU_IMPORT

        private void menuClearTestCase_Click(object sender, EventArgs e)
        {
            //跳出是對話框詢問使用者確定清除?是的話清除，否的話不動作
            if (SystemSetting.isEngineer || SystemSetting.isAdmin)
            {
                DialogResult result = MessageBox.Show(
                    "是否清除所有測試項目?", // 消息文本
                    "警告", // 标题
                    MessageBoxButtons.YesNo, // 按钮类型
                    MessageBoxIcon.Warning // 图标类型
                );
                if (result == DialogResult.Yes)
                {
                    //清空所有list view item
                    lvTest.Invoke(new MethodInvoker(() => { lvTest.Items.Clear(); }));
                    //清空所有測試項目
                    dM.AfterCommuConfigs.Clear();
                    dM.AfterTestCase.Clear();
                    dM.AfterTestParams.Clear();
                    dM.BeforeCommuConfigs.Clear();
                    dM.BeforeTestCase.Clear();
                    dM.BeforeTestParams.Clear();
                    dM.AllCommuConfigs.Clear();
                    dM.AllTestCases.Clear();
                    dM.AllTestParams.Clear();

                    //清除所有測試項目檔案以及INI設定
                    SystemSetting.testCasePath = "";
                    SystemSetting.beforeTestCasePath = "";
                    SystemSetting.afterTestCasePath = "";
                    dM.CaseCfgPathHandler(SystemSetting.testCasePath, "", SystemSetting.TEST_CFG_PATH);
                    dM.CaseCfgPathHandler(SystemSetting.beforeTestCasePath, "", SystemSetting.BEFORE_TEST_CASE_PATH);
                    dM.CaseCfgPathHandler(SystemSetting.afterTestCasePath, "", SystemSetting.AFTER_TEST_CASE_PATH);
                    ShowInfoMsg("測試項目已清除！");
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }
        }

        /// <summary>
        /// menuStrip 档案->汇入->测试项目
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void menuImportTestCase_Click(object sender, EventArgs e) //Has log
        {
            if (SystemSetting.isEngineer || SystemSetting.isAdmin)
            {
                InfoLogAppend("Import test case ......", SystemState.AppSetting);
                string title = "Open Test Case File";
                string filter = "Json Files (*.json)|*.json";
                string filepath = OpenSaveDialog(title, filter);
                SystemSetting.testCasePath = filepath;
                dM.CaseCfgPathHandler(SystemSetting.testCasePath, filepath, SystemSetting.TEST_CFG_PATH);
                bool loadcase =
                    dM.LoadTestCases(filepath, dM.AllTestCases, dM.AllCommuConfigs, dM.AllTestParams); //讀取設定內容到List資料結構
                if (loadcase)
                {
                    Retry_Counter = new int[dM.AllTestCases.Count];
                    dM.RefreshListViewCases();
                    InfoLogAppend("Import test case finished! ", SystemState.AppSetFinish);
                    ShowInfoMsg("測試項目已完成匯入!");
                }
                else
                {
                    WarnLogAppend("Import test case failed! ", SystemState.WarningHandling);
                    ShowWarnMsg("測試項目匯入失敗!");
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }

            tbBarCode.Focus();
        }

        private void menuBeforeCase_Click(object sender, EventArgs e)
        {
            if (SystemSetting.isEngineer || SystemSetting.isAdmin)
            {
                InfoLogAppend("Import before test case ......", SystemState.AppSetting);
                string title = "Open Before Test Case File";
                string filter = "Json Files (*.json)|*.json";
                string filepath = OpenSaveDialog(title, filter);
                SystemSetting.beforeTestCasePath = filepath;
                dM.CaseCfgPathHandler(SystemSetting.beforeTestCasePath, filepath, SystemSetting.BEFORE_TEST_CASE_PATH);
                bool loadcase = dM.LoadTestCases(filepath, dM.BeforeTestCase, dM.BeforeCommuConfigs,
                    dM.BeforeTestParams); //讀取設定內容到List資料結構
                if (loadcase)
                {
                    dM.RefreshListViewCases();
                    InfoLogAppend("Import before test case finished! ", SystemState.AppSetFinish);
                    ShowInfoMsg("前測項目已完成匯入!");
                }
                else
                {
                    WarnLogAppend("Import before test case failed! ", SystemState.WarningHandling);
                    ShowWarnMsg("前測項目匯入失敗!");
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }
        }

        private void menuAfterCase_Click(object sender, EventArgs e)
        {
            if (SystemSetting.isEngineer || SystemSetting.isAdmin)
            {
                InfoLogAppend("Import after test case ......", SystemState.AppSetting);
                string title = "Open After Test Case File";
                string filter = "Json Files (*.json)|*.json";
                string filepath = OpenSaveDialog(title, filter);
                SystemSetting.afterTestCasePath = filepath;
                dM.CaseCfgPathHandler(SystemSetting.afterTestCasePath, filepath, SystemSetting.AFTER_TEST_CASE_PATH);
                bool loadcase =
                    dM.LoadTestCases(filepath, dM.AfterTestCase, dM.AfterCommuConfigs,
                        dM.AfterTestParams); //讀取設定內容到List資料結構
                if (loadcase)
                {
                    dM.RefreshListViewCases();
                    InfoLogAppend("Import after test case finished! ", SystemState.AppSetFinish);
                    ShowInfoMsg("後測項目已完成匯入!");
                }
                else
                {
                    WarnLogAppend("Import after test case failed! ", SystemState.WarningHandling);
                    ShowWarnMsg("後測項目匯入失敗!");
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }
        }

        #endregion

        #region Loading Form

        /// <summary>
        /// 修改加载窗口信息
        /// </summary>
        /// <param name="message">信息</param>
        public void UpdateLoadingForm(string message)
        {
            if (loadingFormOpen)
            {
                loadingForm.AppendMessage(message);
            }
        }

        /// <summary>
        /// LoadingForm打开的情况下,打印相关信息
        /// </summary>
        /// <param name="message">信息</param>
        /// <param name="isError">是否报错</param>
        public void UpdateLoadingForm(string message, bool isError)
        {
            if (loadingFormOpen)
            {
                loadingForm.AppendMessage(message, isError);
            }
        }

        /// <summary>
        /// 关闭加载窗口 : 异步关闭
        /// </summary>
        public void CloseLoadingForm()
        {
            if (loadingFormOpen)
            {
                loadingFormOpen = false;
                loadingForm.Invoke(new Action(loadingForm.Close));
            }
        }

        #endregion

        #region Main Form Control

        public MainForm()
        {
            InitializeComponent();
            AppStartTime = DateTime.Now;
            Instance = this;

            //Init Log
            logger = new LogContent();
            logger.LogUpdated += AppLogger;
            SystemSetting.defaultAppLogPath = Application.StartupPath; // 日志默认路径为app启动路径
            SystemSetting.appLogPath = SystemSetting.defaultAppLogPath;
            SystemSetting.appLogFilename = $"{SystemSetting.SOFTWARE_NAME}" + $"{SystemSetting.SOFTWARE_VERSION}" + $"_{InitialTime}" + "_AppLog" + ".txt";
            
            // 创建app日志文件
            CreateAppLogFile();
            InfoLogAppend($"Start: [{SystemSetting.SOFTWARE_NAME} - {SystemSetting.SOFTWARE_VERSION}] -- Device Name: {Environment.MachineName} -- .NET Version: {Environment.Version}", SystemState.Initiliazing);

            dM = new DataManager(this);

            InfoLogAppend("Setting system value ......", SystemState.SetValue);
            try
            {
                //Backup init Component size & location
                formInitialSize = this.ClientSize;
                groupBox1InitialSize = this.groupBoxStartButton.ClientSize;
                groupBox2InitialSize = this.groupBoxResultDisplay.ClientSize;
                groupBox3InitialSize = this.groupBoxListView.ClientSize;
                groupBox4InitialSize = this.groupBoxStatusDisplay.ClientSize;
                tbMsgInitialSize = this.tbMsg.ClientSize;
                tbLogInitialSize = this.tbLog.ClientSize;
                tbResultInitialSize = this.tbResult.ClientSize;
                lvTestInitialSize = this.lvTest.ClientSize;
                btnStartInitialSize = this.btnStart.ClientSize;
                btnClearInitialSize = this.btnClear.ClientSize;
                picLogoInitialSize = this.picLogo.ClientSize;
                btnStartFontInitialSize = this.btnStart.Font.Size;
                btnClearFontInitialSize = this.btnClear.Font.Size;
                tbResultFontInitailSize = this.tbResult.Font.Size;

                groupBoxStartButtonLocation = this.groupBoxStartButton.Location;
                groupBoxResultDisplayLocation = this.groupBoxResultDisplay.Location;
                groupBox3Location = this.groupBoxListView.Location;
                groupBox4Location = this.groupBoxStatusDisplay.Location;
                tbLogLocation = this.tbLog.Location;
                lbSnLocation = this.lbSN.Location;
                tbBarcodeLocation = this.tbBarCode.Location;
                tbPassLocation = this.tbPass.Location;
                tbFailLocation = this.tbFail.Location;
                tbTotalLocation = this.tbTotal.Location;
                tbPercentageLocation = this.tbPercentage.Location;
                btnCLearLocation = this.btnClear.Location;
                picLogoLocation = this.picLogo.Location;

                //Window title display software name & version
                this.Text = $"{SystemSetting.SOFTWARE_NAME} - Ver: {SystemSetting.SOFTWARE_VERSION}";
                ThisProcess = ProcessHandler.GetProcessByName(SystemSetting.SOFTWARE_NAME);

                //System setting
                SystemSetting.isAdmin = false;
                IsTestData = false;
                isTesting = false;
                delayToStart = 1000;
                InfoLogAppend("Successfully set system value.", SystemState.SetValueSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Fail to setting system value!", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg("Unexpected error occur while setting system value!" + Environment.NewLine + ex.Message);
            }

            //open loading window
            loadingForm = new LoadingForm();
            loadingForm.TopMost = true; // 窗体将始终置顶（即使失去焦点也会保持在最上层）
            loadingFormOpen = true;
            loadingForm.Show();

            //開啟多執行序處理&顯示載入訊息 开启多线程显示载入信息
            Task.Run(() => dM.LoadingData());
            string vsCfgPath = Path.Combine(Application.StartupPath, SystemSetting.VISUALIZER_CFG_FILENAME);
            LoadVisualizerCFG(vsCfgPath);
            //Start thread to ask device to start test 启动请求设备的线程开始测试
            threadAskDevice = new Thread(AskDeviceToStartTest);
            threadAskDevice.Start();
        }

        /// <summary>
        /// 请求设备,准备测试
        /// </summary>
        private void AskDeviceToStartTest()
        {
            InfoLogAppend("Start new thread to ask device to start test......", SystemState.AppSetting);
            while (!KillThreadAskDevice)
            {
                if (String.IsNullOrEmpty(SystemSetting.deviceCMDCheck))
                {
                    continue;
                }
                if (!bool.Parse(SystemSetting.deviceCMDCheck))
                {
                    continue;
                }
                if (AllowTestStart)
                {
                    try
                    {
                        if (KillThreadAskDevice)
                        {
                            break;
                        } //Kill thread

                        string portNameAsk = SystemSetting.askDevicePort;
                        string portNameReturn = SystemSetting.askDeviceReturnPort;
                        //Check if all serial port is available

                        if (KillThreadAskDevice)
                        {
                            break;
                        } //Kill thread

                        if (DataManager.IsPortAvailable && SerialPortHandler.PortIsExist(portNameAsk))
                        {
                            //Send command to device to ask if start test
                            int freq = (int)(decimal.Parse(SystemSetting.askDeviceFreq) * 1000);
                            string[] cmdStr = SystemSetting.askDeviceCMD.Split(' ');
                            byte[] cmd = new byte[cmdStr.Length];
                            cmd = cmdStr.Select(s => Convert.ToByte(s, 16)).ToArray();
                            if (KillThreadAskDevice)
                            {
                                break;
                            } //Kill thread

                            //Get port in list
                            SerialPort port = new SerialPort();
                            for (int i = 0; i < dM.serialPorts.Count; i++)
                            {
                                if (dM.serialPorts[i].PortName == portNameAsk)
                                {
                                    port = dM.serialPorts[i];
                                    break;
                                }
                            }

                            if (KillThreadAskDevice)
                            {
                                break;
                            } //Kill thread

                            //Send command to device
                            if (port.IsOpen)
                            {
                                port.Write(cmd, 0, cmd.Length);
                                Thread.Sleep(freq);
                            }
                            else
                            {
                                WarnLogAppend(
                                    $"Fail to ask device to start test due to port: {port.PortName} not open!!",
                                    SystemState.WarningHandling);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        ErrorLogAppend("Fail to ask device to start test!", ex.Message + ex.StackTrace,
                            SystemState.ErrorHandling);
                        ShowErrorMsg("Unexpected error occur while asking device to start test!" + Environment.NewLine +
                                     ex.Message);
                    }
                }
            }
        }

        /// <summary>
        /// 主窗体加载完前执行,常用于初始化窗体数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            InfoLogAppend("Setting main form UI component......", SystemState.UISetting);
            try
            {
                //Groupbox color 订阅 Paint 事件
                groupBoxStartButton.Paint += groupboxPaint.groupbox_Paint;
                groupBoxResultDisplay.Paint += groupboxPaint.groupbox_Paint;
                groupBoxListView.Paint += groupboxPaint.groupbox_Paint;
                groupBoxStatusDisplay.Paint += groupboxPaint.groupbox_Paint;
                tbResult.Text = System_msg.NEED_LOGIN_STR;
                SystemSetting.loginUserName = System_msg.NO_LOGIN_STR;
                InfoLogAppend("Setting main form UI component finished", SystemState.UISetSuccess);
            }
            catch (IOException ex)
            {
                ErrorLogAppend("IOException while setting main form UI component!", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg("IOException error occur while setting main form UI component!" + Environment.NewLine +
                             ex.Message);
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Unespected error occur while setting UI component!", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg("Unexpected error occur while setting UI component!" + Environment.NewLine + ex.Message);
            }
        }

        /// <summary>
        /// 主窗体关闭前执行,常用于释放资源
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            InfoLogAppend($"Shutting down {SystemSetting.SOFTWARE_NAME}......", SystemState.ShuttingDown);
            try
            {
                //關閉所有visualizer
                while (visualizerForms.Count != 0)
                {
                    string title = visualizerForms.ElementAt(0).Key;
                    visualizerForms[title].Close();
                }

                this.TopMost = false;
                //背景執行關閉程序
                this.WindowState = FormWindowState.Minimized;

                updateVisualizer = false;
                KillThreadAskDevice = true;
                testTask = TestTask.FinishProcess;
                Thread.Sleep(2000);
                dM.CloseAllSerialPort();

                //檢查執行序是否還在執行
                if (threadTest != null && threadTest.IsAlive)
                {
                    threadTest.Abort(); //強制停止執行序
                    threadTest.Join(); //等待執行序終止
                }

                // 检查 Visualizer线程 是否还在执行
                if (threadVisualizer != null && threadVisualizer.IsAlive)
                {
                    updateVisualizer = false;
                    Thread.Sleep(1000);
                    threadVisualizer.Abort();
                    threadVisualizer.Join();
                }

                // 检查访问设备线程是否还在执行
                if (threadAskDevice != null && threadAskDevice.IsAlive)
                {
                    KillThreadAskDevice = true;
                    Thread.Sleep(1000);
                    threadAskDevice.Abort();
                    threadAskDevice.Join();
                }
                WarnLogAppend("Thread aborted", SystemState.WarningHandling);
                TimeSpan runtime = DateTime.Now - AppStartTime;

                // 格式化为 HH:mm:ss:ms
                string formattedRuntime = string.Format("{0:D2}:{1:D2}:{2:D2}:{3:D3}",
                    (int)runtime.TotalHours,
                    runtime.Minutes,
                    runtime.Seconds,
                    runtime.Milliseconds);

                InfoLogAppend($"{SystemSetting.SOFTWARE_NAME} shutdown success! -- System Runtime: {formattedRuntime}",
                    SystemState.LogEnd);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Unespected error occur while shuting down {SystemSetting.SOFTWARE_NAME} !",
                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 窗体大小改变时,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Resize(object sender, EventArgs e)
        {
            InfoLogAppend("Try to resize main form UI component......", SystemState.UISetting);
            try
            {
                // 宽的比例
                float widthRatio = (float)this.ClientSize.Width / formInitialSize.Width;
                // 高的比例
                float heightRatio = (float)this.ClientSize.Height / formInitialSize.Height;

                //Reset size 重新设置大小 size
                groupBoxStartButton.Size = new Size((int)(groupBox1InitialSize.Width * widthRatio),
                    (int)(groupBox1InitialSize.Height * heightRatio));
                groupBoxResultDisplay.Size = new Size((int)(groupBox2InitialSize.Width * widthRatio),
                    (int)(groupBox2InitialSize.Height * heightRatio));
                groupBoxListView.Size = new Size((int)(groupBox3InitialSize.Width * widthRatio),
                    (int)(groupBox3InitialSize.Height * heightRatio));
                groupBoxStatusDisplay.Size = new Size((int)(groupBox4InitialSize.Width * widthRatio),
                    (int)(groupBox4InitialSize.Height * heightRatio));
                tbLog.Size = new Size((int)(tbLogInitialSize.Width * widthRatio),
                    (int)(tbLogInitialSize.Height * heightRatio));
                tbMsg.Size = new Size((int)(tbMsgInitialSize.Width * widthRatio),
                    (int)(tbMsgInitialSize.Height * heightRatio));
                tbResult.Size = new Size((int)(tbResultInitialSize.Width * widthRatio),
                    (int)(tbResultInitialSize.Height * heightRatio));
                lvTest.Size = new Size((int)(lvTestInitialSize.Width * widthRatio),
                    (int)(lvTestInitialSize.Height * heightRatio));
                btnStart.Size = new Size((int)(btnStartInitialSize.Width * widthRatio),
                    (int)(btnStartInitialSize.Height * heightRatio));
                btnClear.Size = new Size((int)(btnClearInitialSize.Width * widthRatio),
                    (int)(btnClearInitialSize.Height * heightRatio));
                picLogo.Size = new Size((int)(picLogoInitialSize.Width * widthRatio),
                    (int)(picLogoInitialSize.Height * heightRatio));

                //Reset location 重新设置位置 location
                groupBoxStartButton.Location = new Point((int)(groupBoxStartButtonLocation.X * widthRatio),
                    (int)(groupBoxStartButtonLocation.Y * heightRatio));
                groupBoxResultDisplay.Location = new Point((int)(groupBoxResultDisplayLocation.X * widthRatio),
                    (int)(groupBoxResultDisplayLocation.Y * heightRatio));
                groupBoxListView.Location = new Point((int)(groupBox3Location.X * widthRatio),
                    (int)(groupBox3Location.Y * heightRatio));
                groupBoxStatusDisplay.Location = new Point((int)(groupBox4Location.X * widthRatio),
                    (int)(groupBox4Location.Y * heightRatio));
                tbLog.Location = new Point((int)(tbLogLocation.X * widthRatio), (int)(tbLogLocation.Y * heightRatio));
                lbSN.Location = new Point((int)(lbSnLocation.X * widthRatio), (int)(lbSnLocation.Y * heightRatio));
                tbBarCode.Location = new Point((int)(tbBarcodeLocation.X * widthRatio),
                    (int)(tbBarcodeLocation.Y * heightRatio));
                tbPass.Location = new Point((int)(tbPassLocation.X * widthRatio),
                    (int)(tbPassLocation.Y * heightRatio));
                tbFail.Location = new Point((int)(tbFailLocation.X * widthRatio),
                    (int)(tbFailLocation.Y * heightRatio));
                tbTotal.Location = new Point((int)(tbTotalLocation.X * widthRatio),
                    (int)(tbTotalLocation.Y * heightRatio));
                tbPercentage.Location = new Point((int)(tbPercentageLocation.X * widthRatio),
                    (int)(tbPercentageLocation.Y * heightRatio));
                btnClear.Location = new Point((int)(btnCLearLocation.X * widthRatio),
                    (int)(btnCLearLocation.Y * heightRatio));
                picLogo.Location = new Point((int)(picLogoLocation.X * widthRatio),
                    (int)(picLogoLocation.Y * heightRatio));

                //Font Size
                float newFontSize = tbResultFontInitailSize * Math.Min(widthRatio, heightRatio); // 取最小比例
                if (newFontSize > 0)
                {
                    tbResult.Font = new Font(tbResult.Font.FontFamily, newFontSize);
                } // 設置新字體大小

                newFontSize = btnClearFontInitialSize * Math.Min(widthRatio, heightRatio);
                if (newFontSize > 0)
                {
                    btnStart.Font = new Font(btnStart.Font.FontFamily, newFontSize);
                }

                newFontSize = btnClearFontInitialSize * Math.Min(widthRatio, heightRatio);
                if (newFontSize > 0)
                {
                    btnClear.Font = new Font(btnClear.Font.FontFamily, newFontSize);
                }

                InfoLogAppend("Successfully resize main form UI component. ", SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Fail to resize main form UI component!", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 主窗体获得焦点,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Activated(object sender, EventArgs e)
        {
            // 聚焦到扫码枪录入口
            tbBarCode.Focus();
        }

        /// <summary>
        /// 点击主窗体,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Click(object sender, EventArgs e)
        {
            // 聚焦到扫码枪录入口
            tbBarCode.Focus();
        }

        /// <summary>
        /// 鼠标指针在控件或窗体上移动时触发的
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_MouseMove(object sender, MouseEventArgs e)
        {
            if (!tbBarCode.Focused)
            {
                tbBarCode.Focus();
            }
        }
        #endregion

        #region Info Display
        /// <summary>
        /// 显示工作集信息 : 异步实时更新
        /// </summary>
        public void DisplayWorkerInfo() //Has log
        {
            InfoLogAppend("Try to update worker info......", SystemState.UISetting);
            try
            {
                tbMsg.Invoke(new Action(() =>
                {
                    tbMsg.Text = $"{System_msg.LOGIN_USER_STR} : {SystemSetting.loginUserName} , " +
                                 $"{System_msg.WORKER_ID_STR} : {SystemSetting.workId} , " +
                                 $"{System_msg.MACHINE_ID_STR} : {SystemSetting.machineId} , " +
                                 $"{System_msg.WORK_ORDER_STR} : {SystemSetting.workOrder} , " +
                                 $"{System_msg.PROCESS_ORDER_STR} : {SystemSetting.processOrder}";
                }));
                InfoLogAppend("Successfully update worker info. ", SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Fail to update worker info", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 显示结果 : 异步实时更新
        /// </summary>
        public void DisplayResult() //Has log
        {
            InfoLogAppend("Try to update result display......", SystemState.UISetting);
            try
            {
                tbPass.Invoke(new Action(() => { tbPass.Text = $"{System_msg.PASS_STR} : {passNum}"; }));
                tbFail.Invoke(new Action(() => { tbFail.Text = $"{System_msg.FAIL_STR} : {failNum}"; }));
                tbTotal.Invoke(new Action(() => { tbTotal.Text = $"{System_msg.TOTAL_STR} : {totalNum}"; }));
                tbPercentage.Invoke(new Action(() => { tbPercentage.Text = $"{System_msg.PERCETNAGE_STR} : {yeild} %"; }));
                InfoLogAppend("Successfully update result display. ", SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend("Fail to update result display", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg("Unexpected error occur while updating result display!");
            }
        }

        /// <summary>
        /// 修改测试结果显示控件ListView : lvTest
        /// </summary>
        /// <param name="name"></param>
        /// <param name="resultText"></param>
        /// <param name="color"></param>
        private void UpdateLVTest(string name, string resultText, Color color)
        {
            lvTest.ItemChecked -= lvTest_ItemChecked;
            try
            {
                InfoLogAppend("Try to update text of test status in list view......", SystemState.UISetting);
                lvTest.Invoke(new MethodInvoker(() =>
                {
                    for (int i = 0; i < lvTest.Items.Count; i++)
                    {
                        if (lvTest.Items[i].SubItems[(int)POS_ListViewSub.NAME_POS].Text == name)
                        {
                            lvTest.Items[i].SubItems[(int)POS_ListViewSub.TEST_RESULT_POS].Text = resultText;
                            lvTest.Items[i].BackColor = color;
                        }
                    }
                }));
                InfoLogAppend($"Successfully update text of test status in list view lvTest. ",
                    SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to update text of test rstatus in list view lvTest.", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
            }
            finally
            {
                lvTest.ItemChecked += lvTest_ItemChecked;
            }
        }

        /// <summary>
        /// 修改lvTest ListView内容
        /// </summary>
        /// <param name="name"></param>
        /// <param name="resultValue"></param>
        /// <param name="resultText"></param>
        /// <param name="color"></param>
        private void UpdateLVTest(string name, string resultValue, string resultText, Color color)
        {
            lvTest.ItemChecked -= lvTest_ItemChecked;
            try
            {
                InfoLogAppend("Try to update test result in list view......", SystemState.UISetting);
                lvTest.Invoke(new MethodInvoker(() =>
                {
                    for (int i = 0; i < lvTest.Items.Count; i++)
                    {
                        if (lvTest.Items[i].SubItems[(int)POS_ListViewSub.NAME_POS].Text == name)
                        {
                            lvTest.Items[i].SubItems[(int)POS_ListViewSub.TEST_RESULT_POS].Text = resultText;
                            lvTest.Items[i].SubItems[(int)POS_ListViewSub.TEST_VALUE_POS].Text = resultValue;
                            lvTest.Items[i].BackColor = color;
                        }
                    }
                }));
                InfoLogAppend($"Successfully update test result in list view lvTest.", SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to update test result in list view lvTest.", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
            }
            finally
            {
                lvTest.ItemChecked += lvTest_ItemChecked;
            }
        }

        /// <summary>
        /// 更新tbResult的TextBox状态
        /// </summary>
        /// <param name="resultText"></param>
        /// <param name="color"></param>
        public void UpdateTbResult(string resultText, Color color)
        {
            try
            {
                InfoLogAppend($"Try to update test result in text box: [{resultText}, {color}]", SystemState.UISetting);
                tbResult.Invoke(new MethodInvoker(() =>
                {
                    tbResult.Text = resultText;
                    tbResult.BackColor = color;
                }));
                InfoLogAppend($"Successfully update test result in text box: [{resultText}, {color}] ",
                    SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to update test result in text box: [{resultText}, {color}]",
                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 更新tbResult结果展示,tbBarCode扫码枪
        /// </summary>
        /// <param name="resultText"></param>
        /// <param name="color"></param>
        /// <param name="barcode"></param>
        public void UpdateTbResult(string resultText, Color color, string barcode)
        {
            try
            {
                InfoLogAppend($"Try to update test result in text box: [{resultText}, {color}, {barcode}]",
                    SystemState.UISetting);
                tbResult.BeginInvoke(new MethodInvoker(() =>
                {
                    tbResult.Text = resultText;
                    tbResult.BackColor = color;
                }));
                tbBarCode.BeginInvoke(new MethodInvoker(() => { tbBarCode.Text = barcode; }));
                InfoLogAppend($"Successfully update test result in text box: [{resultText}, {color}, {barcode}]",
                    SystemState.UISetSuccess);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to update test result in text box: [{resultText}, {color}, {barcode}]",
                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
            }
        }

        public void ShowErrorMsg(string msg)
        {
            MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        public void ShowWarnMsg(string msg)
        {
            MessageBox.Show(msg, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }

        public void ShowInfoMsg(string msg)
        {
            MessageBox.Show(msg, "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        public void AppendTextToTbLog(string newText)
        {
            tbLog.Invoke(new MethodInvoker(() =>
            {
                tbLog.AppendText($"{DateTime.Now.ToString("HH:mm:ss:fff")} : {newText}" +
                                 Environment.NewLine); // 添加新文字並換行
            }));
        }

        private void PreventInputKeyPress(object sender, KeyPressEventArgs e)
        {
            // 阻止用户输入
            e.Handled = true;
        }

        private void ForceLostFocus(object sender, MouseEventArgs e)
        {
            // 阻止焦点转移
            this.ActiveControl = null; // 使当前控件失去焦点
        }

        #endregion

        #region Function

        /// <summary>
        /// 开始检测
        /// </summary>
        private void RunStartTest()
        {
            try
            {
                btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));
                if (!AllowTestStart)
                {
                    WarnLogAppend("Test did not start! System not allow to start the test! ",
                        SystemState.WarningHandling);
                    btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));
                    return;
                }
                AllowTestStart = false;

                SetForegroundWindow(ThisProcess.MainWindowHandle);

                if (isTesting)
                {
                    ShowWarnMsg("測試正在執行中,請稍候。");
                    WarnLogAppend("Test task aborted! On-going test task detected! ", SystemState.WarningHandling);
                    btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));
                    return;
                }

                if (!DataManager.IsPortAvailable)
                {
                    ShowWarnMsg("部分串口未正確開啟，請檢查串口連線是否正常!");
                    WarnLogAppend("Test task aborted! Serial port not available! ", SystemState.WarningHandling);
                    btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));
                    return;
                }

                //Init parameter
                isTesting = true;
                IsTestData = false;

                testTask = TestTask.Idle;

                //update message
                UpdateTbResult(System_msg.TESTING_STR, Color.Yellow);


                //Timeout setting
                countdownTimer = new Timer(timeoutInterval);
                countdownTimer.Elapsed += OnTimedEvent;
                countdownTimer.AutoReset = true;

                //Thread setting
                AppendTextToTbLog("Start testing");
                threadTest = new Thread(RunTestProcess);
                threadTest.Start();
            }
            catch (Exception ex)
            {
                btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));
                AllowTestStart = true;

                Console.WriteLine($"{ex.Message}");
                ErrorLogAppend("Test task aborted! Fail to start test task!", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
            }
        }

        #endregion

        #region Timer

        private static void SetTimeout(int timeout)
        {
            resendCount = 0;
            countdownTime = timeout;
            Console.WriteLine($"設定Timeout {countdownTime}");
        }

        private static void StartTimer()
        {
            isPaused = false;
            isTimeout = false;
            resendCount = 0;
            countdownTimer.Start();
            Console.WriteLine("計時開時");
        }

        /// <summary>
        /// Timer暂停
        /// </summary>
        private static void PauseTimer()
        {
            if (!isPaused)
            {
                countdownTimer.Stop();
                isPaused = true;
                Console.WriteLine("計時暫停！");
                return;
            }

            WarnLogAppend("Timer is already paused!", SystemState.WarningHandling);
        }

        private static void ResumeTimer()
        {
            InfoLogAppend($"Try to resume timer", SystemState.SetValue);
            if (isPaused)
            {
                countdownTimer.Start();
                isPaused = false;
                Console.WriteLine("計時繼續！");
                InfoLogAppend($"Successfully resume timer", SystemState.SetValueSuccess);
                return;
            }

            WarnLogAppend("Timer is not paused!", SystemState.WarningHandling);
        }

        /// <summary>
        /// System.Timers.Timer 是一个基于服务器的定时器，它可以按指定的时间间隔引发 Elapsed 事件。
        /// 该定时器使用回调机制，并且是多线程的，适合在服务器端应用程序中使用
        /// 定时器启动后,每0.1s触发该事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void OnTimedEvent(object sender, ElapsedEventArgs e)
        {
            if (countdownTime > 0)
            {
                resendCount++;
                countdownTime--;
                //Instance.AppendTextToTbLog($"Time left: {countdownTime} , Resend count: {resendCount}");
            }
            else
            {
                countdownTimer.Stop();
                isTimeout = true;
                if (Instance.isTesting)
                {
                    Instance.AppendTextToTbLog("Timeout！");
                }
            }
        }

        #endregion

        #region Testing 整个测试过程

        //測試排程 动作入口
        private void RunTestProcess()
        {
            InfoLogAppend("Test task start......", SystemState.TestTaskExecute);
            try
            {
                tbLog.Invoke(new MethodInvoker(() => { tbLog.Clear(); }));
                AppendTextToTbLog($"Delay {delayToStart}ms to start the test...");
                Thread.Sleep(delayToStart);

                while (testTask != TestTask.FinishProcess)
                {
                    switch (testTask)
                    {
                        case TestTask.Idle:
                            AppendTextToTbLog($"Task: {testTask}");
                            //測項預設都為FAIL

                            AppendTextToTbLog("Buffer set and clear.");

                            //Reset parameter
                            for (int i = 0; i < dM.serialPorts.Count; i++)
                            {
                                serialBufferReady[dM.serialPorts[i].PortName] = false;
                            }
                            SP_ReadCounter = 0;
                            finalResult = -1;
                            //CaseID = 0;
                            TestCaseID = 0;
                            PreTestCaseID = 0;
                            PostTestCaseID = 0;
                            IsTestData = false;
                            AppendTextToTbLog($"Reset parameter.");

                            //clear serial port buffer

                            testTask = TestTask.RunPreSetting;

                            break;
                        case TestTask.RunPreSetting:
                            AppendTextToTbLog($"Task: {testTask}, Pre-Test: {dM.BeforeTestCase[PreTestCaseID].Name}");
                            bool isSkip = !Convert.ToBoolean(dM.BeforeTestParams[PreTestCaseID].CaseChecked);
                            if (isSkip)
                            {
                                AppendTextToTbLog($"Skip pre-Test: {dM.BeforeTestCase[PreTestCaseID].Name}");
                                UpdateLVTest(dM.BeforeTestCase[PreTestCaseID].Name, System_msg.SKIP_STR,
                                    Color.LightGray);
                                PreTestCaseID++;
                                if (PreTestCaseID < dM.BeforeTestCase.Count)
                                {
                                    testTask = TestTask.RunPreSetting;
                                    break;
                                }
                                else
                                {
                                    PreTestCaseID = 0;
                                    testTask = TestTask.Preprocess;
                                    break;
                                }
                            }

                            if (dM.BeforeCommuConfigs[PreTestCaseID].PortID != -1)
                            {
                                serialport = dM.serialPorts[dM.BeforeCommuConfigs[PreTestCaseID].PortID];
                                AppendTextToTbLog(
                                    $"Pre-Test: {dM.BeforeTestCase[PreTestCaseID].Name} target serial port: {serialport.PortName}");
                                SendDeviceCommand(serialport, PreTestCaseID, dM.BeforeCommuConfigs, dM.BeforeTestCase);
                            }

                            if (int.TryParse(dM.BeforeTestParams[PreTestCaseID].Delay, out int delay))
                            {
                                AppendTextToTbLog($"Set delay to {dM.BeforeTestParams[PreTestCaseID].Delay}ms.");
                                Thread.Sleep(int.Parse(dM.BeforeTestParams[PreTestCaseID].Delay));
                            }

                            UpdateLVTest(dM.BeforeTestCase[PreTestCaseID].Name, System_msg.PROCESSED_STR,
                                Color.LightGray);
                            PreTestCaseID++;
                            if (PreTestCaseID < dM.BeforeTestCase.Count)
                            {
                                testTask = TestTask.RunPreSetting;
                                break;
                            }
                            else
                            {
                                PreTestCaseID = 0;
                                testTask = TestTask.Preprocess;
                                break;
                            }
//--------------------------------------------------------------------------------------------------------------------------------------------------------
                        // 處理測試是否需要供電
                        case TestTask.Device_CMD:
                            AppendTextToTbLog($"Task: {testTask}, TestCase: {dM.AllTestCases[TestCaseID].Name}");
                            serialport = dM.serialPorts[dM.AllCommuConfigs[TestCaseID].PortID];
                            AppendTextToTbLog(
                                $"TestCase: {dM.AllTestCases[TestCaseID].Name} target serial port: {serialport.PortName}");
                            SendDeviceCommand(serialport, TestCaseID, dM.AllCommuConfigs, dM.AllTestCases);
                            UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.PROCESSED_STR, Color.LightGray);
                            AppendTextToTbLog($"Set delay to {dM.AllTestParams[TestCaseID].Delay}ms.");
                            Thread.Sleep(int.Parse(dM.AllTestParams[TestCaseID].Delay));
                            TestCaseID++;
                            if (TestCaseID < dM.AllTestCases.Count)
                            {
                                testTask = TestTask.Preprocess;
                                break;
                            }
                            else
                            {
                                testTask = TestTask.DisplayResult;
                                break;
                            }

//--------------------------------------------------------------------------------------------------------------------------------------------------------
                        // 測項預處理(刷新ListView以及傳送指令到周邊設備)
                        case TestTask.Preprocess:
                            try
                            {
                                AppendTextToTbLog($"Task: {testTask}, TestCase: {dM.AllTestCases[TestCaseID].Name}");
                                //檢查是否Skip
                                isSkip = !Convert.ToBoolean(dM.AllTestParams[TestCaseID].CaseChecked);
                                if (isSkip)
                                {
                                    UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.SKIP_STR, Color.LightGray);
                                    AppendTextToTbLog($"Skip test case: {dM.AllTestCases[TestCaseID].Name}");
                                    TestCaseID++;
                                    if (TestCaseID < dM.AllTestCases.Count)
                                    {
                                        testTask = TestTask.Preprocess;
                                        break;
                                    }
                                    else
                                    {
                                        testTask = TestTask.DisplayResult;
                                        break;
                                    }
                                }

                                //檢查是否為周邊設備通訊項
                                if (int.Parse(dM.AllTestCases[TestCaseID].TestParamType) == DataManager.DeviceCMD)
                                {
                                    testTask = TestTask.Device_CMD;
                                    break;
                                }

                                //刷新ListVIew
                                UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.TESTING_STR, Color.Yellow);
                                PauseTimer();

                                serialport = dM.serialPorts[dM.AllCommuConfigs[TestCaseID].PortID];
                                AppendTextToTbLog(
                                    $"TestCase: {dM.AllTestCases[TestCaseID].Name} target serial port: {serialport.PortName}");
                                serialBufferReady[serialport.PortName] = false;
                                int portID = dM.AllCommuConfigs[TestCaseID].PortID;
                                int.TryParse(dM.AllTestParams[TestCaseID].AvgAmount, out int avg);
                                if (avg != 0)
                                {
                                    AvgCheckValue = new List<double>();
                                    //設定熔斷點
                                    AvgCheckLimiter = (int)(int.Parse(dM.AllTestParams[TestCaseID].UpperLimit) *
                                                            LimiterRatio);
                                    limiterIndex = 0;
                                    /*
                                    //當熔斷觸發次數佔整體1/4時，判定失敗
                                    limiterMaxCount = (int)((dataManager.AllTestParams[CaseID].AvgAmount)*0.25);
                                    */
                                }

                                SendDeviceCommand(serialport, TestCaseID, dM.AllCommuConfigs, dM.AllTestCases);

                                IsTestData = true;
                                IsNotResponse = true;

                                SetTimeout(int.Parse(dM.AllTestParams[TestCaseID].Timeout) / timeoutInterval);
                                AppendTextToTbLog($"Timeout set to: {dM.AllTestParams[TestCaseID].Timeout}.");
                                StartTimer();
                                ResultValue = "";
                                testTask = TestTask.Process;
                                AppendTextToTbLog($"Task: {testTask}");
                            }
                            catch (Exception ex)
                            {
                                ErrorLogAppend(
                                    $"Test task aborted! Fail to preprocess test case: {dM.AllTestCases[TestCaseID].Name}",
                                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                                testTask = TestTask.EmergencyStop;
                            }

                            break;
//--------------------------------------------------------------------------------------------------------------------------------------------------------
                        // 處理周邊設備回傳數據並判斷
                        case TestTask.Process:
                            try
                            {
                                serialport = dM.serialPorts[dM.AllCommuConfigs[TestCaseID].PortID];
                                //檢查是否沒回應
                                if (!serialBufferReady[(serialport.PortName)] && (resendCount >= RESEND_DELAY))
                                {
                                    IsNotResponse = true;
                                    resendCount = 0;
                                    serialBufferReady[serialport.PortName] = false;
                                    //重傳指令
                                    AppendTextToTbLog($"No response from {serialport.PortName}. Resending command......");
                                    AppendTextToTbLog($"Set delay to {dM.AllTestParams[TestCaseID].Delay}ms.");
                                    Thread.Sleep(int.Parse(dM.AllTestParams[TestCaseID].Delay));
                                    SendDeviceCommand(serialport, TestCaseID, dM.AllCommuConfigs, dM.AllTestCases);
                                }
                                if (serialBufferReady[(serialport.PortName)])
                                {
                                    AppendTextToTbLog($"SerialBufferReady:{serialport.PortName}");
                                    IsNotResponse = false;
                                    IsTestData = false;
                                    serialBufferReady[serialport.PortName] = false;
                                    PauseTimer();

                                    //檢查測試數據
                                    switch (int.Parse(dM.AllTestCases[TestCaseID].TestParamType))
                                    {
                                        case (int)CaseTestParamType.Float:
                                            AppendTextToTbLog($"Received Data:{BitConverter.ToString(TestResultData).Replace("-", " ")}");
                                            //Modbus
                                            if (IsModbus)
                                            {
                                                if (TestResultData[ModbusSetting.POS_FUNCTION] ==
                                                    ModbusFunction.CMD_READ_MULTI_REGISTER &&
                                                    TestResultData[ModbusSetting.POS_SLAVE_ADDRESS] ==
                                                    Convert.ToByte(dM.AllCommuConfigs[TestCaseID].SlaveID, 16))
                                                {
                                                    CheckFloatData(ReceivedData[ModbusSetting.POS_RX_VALUE0],
                                                        TestResultData[ModbusSetting.POS_RX_VALUE1], TestCaseID);
                                                }
                                            }
                                            //Uart
                                            else
                                            {
                                                if (TestResultData[UartSetting.POS_FUNCTION] ==
                                                    UartFunction.CMD_TEST_EXE &&
                                                    TestResultData[UartSetting.POS_ORDER] == Convert.ToByte(TestCaseID))
                                                {
                                                    CheckFloatData(TestResultData[UartSetting.POS_VALUE0],
                                                        TestResultData[UartSetting.POS_VALUE1], TestCaseID);
                                                }
                                            }

                                            break;

                                        case (int)CaseTestParamType.LogicLevel:
                                            AppendTextToTbLog($"Received Data:{BitConverter.ToString(TestResultData).Replace("-", " ")}");
                                            if (IsModbus)
                                            {
                                                if (TestResultData[ModbusSetting.POS_FUNCTION] ==
                                                    ModbusFunction.CMD_READ_MULTI_REGISTER &&
                                                    TestResultData[ModbusSetting.POS_SLAVE_ADDRESS] ==
                                                    Convert.ToByte(dM.AllCommuConfigs[TestCaseID].SlaveID, 16))
                                                {
                                                    CheckLogicLevel(TestResultData[ModbusSetting.POS_RX_VALUE0],
                                                        TestCaseID);
                                                }
                                            }
                                            else
                                            {
                                                if (TestResultData[UartSetting.POS_FUNCTION] ==
                                                    UartFunction.CMD_TEST_EXE &&
                                                    TestResultData[UartSetting.POS_ORDER] == Convert.ToByte(TestCaseID))
                                                {
                                                    CheckLogicLevel(TestResultData[UartSetting.POS_VALUE0], TestCaseID);
                                                }
                                            }

                                            break;

                                        case (int)CaseTestParamType.CommuCheck:
                                            AppendTextToTbLog(
                                                $"Received Data:{BitConverter.ToString(TestResultData).Replace("-", " ")}");
                                            int checkByteCount =
                                                int.Parse(dM.AllCommuConfigs[TestCaseID].NumOfCheckBytes);
                                            int[] checkBytePOS = dM.AllCommuConfigs[TestCaseID].PosOfCheckBytes
                                                .Split(' ').Select(s => Convert.ToInt32(s)).ToArray();
                                            byte[] checkByteValue = dM.AllTestParams[TestCaseID].NormalValue.Split(' ')
                                                .Select(s => Convert.ToByte(s, 16)).ToArray();
                                            string[] results = new string[checkByteCount];
                                            for (int i = 0; i < checkByteCount; i++)
                                            {
                                                if (TestResultData[checkBytePOS[i]] != checkByteValue[i])
                                                {
                                                    IsPass = false;
                                                    TaskProcessed();
                                                    break;
                                                }
                                                else
                                                {
                                                    IsPass = true;
                                                    TaskProcessed();
                                                }
                                            }

                                            //將結果轉換成單一字串
                                            for (int i = 0; i < checkByteCount; i++)
                                            {
                                                results[i] = TestResultData[checkBytePOS[i]].ToString("X2");
                                            }

                                            ResultValue = string.Join(" ", results);
                                            break;
                                        case (int)CaseTestParamType.AvgCheck:
                                            double value = 0;
                                            int[] positions = dM.AllCommuConfigs[TestCaseID].PosOfCheckBytes.Split(' ')
                                                .Select(s => Convert.ToInt32(s)).ToArray();
                                            if (positions.Length == 1)
                                            {
                                                value = TestResultData[positions[0]];
                                            }
                                            else if (positions.Length == 2)
                                            {
                                                ushort combinedValue = (ushort)((TestResultData[positions[0]] << 8) |
                                                    TestResultData[positions[1]]);
                                                value = (short)combinedValue;
                                            }
                                            else
                                            {
                                                value = InvalideValue;
                                            }

                                            if (value >= AvgCheckLimiter)
                                            {
                                                value = AvgCheckLimiter;
                                                AvgCheckValue.Add(value);
                                                limiterIndex++;
                                            }
                                            else
                                            {
                                                AvgCheckValue.Add(value);
                                            }

                                            if (AvgCheckValue.Count >=
                                                int.Parse(dM.AllTestParams[TestCaseID].AvgAmount))
                                            {
                                                //計算平均值
                                                double avgValue = CalculateAvg(AvgCheckValue.ToArray());
                                                ResultValue = avgValue.ToString();
                                                string doublesString = string.Join(", ", AvgCheckValue.ToArray());
                                                AppendTextToTbLog(
                                                    $"TestCase: {dM.AllTestCases[TestCaseID].Name} get average value: {avgValue} with method: {SystemSetting.AVERAGE_STR}, original raw data: {doublesString}");
                                                //檢查是否超過上下限
                                                if (avgValue > int.Parse(dM.AllTestParams[TestCaseID].UpperLimit) ||
                                                    avgValue < int.Parse(dM.AllTestParams[TestCaseID].LowerLimit))
                                                {
                                                    IsPass = false;
                                                    TaskProcessed();
                                                }
                                                else
                                                {
                                                    IsPass = true;
                                                    TaskProcessed();
                                                }

                                                ResultValue = avgValue.ToString();
                                                limiterIndex = 0;
                                                AvgCheckValue.Clear();
                                            }
                                            else
                                            {
                                                IsTestData = true;
                                                serialBufferReady[serialport.PortName] = true;
                                                SetTimeout(int.Parse(dM.AllTestParams[TestCaseID].Timeout) /
                                                           timeoutInterval);
                                                AppendTextToTbLog(
                                                    $"Timeout set to: {dM.AllTestParams[TestCaseID].Timeout}.");
                                                StartTimer();
                                                testTask = TestTask.Process;
                                                break;
                                            }

                                            break;
                                    }
                                }

                                if (isTimeout)
                                {
                                    IsTestData = false;
                                    UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.TIMEOUT_STR,
                                        System_msg.FAIL_STR, Color.Red);

                                    AppendTextToTbLog($"Test case: {dM.AllTestCases[TestCaseID].Name} - Timeout!!!");

                                    testTask = TestTask.FailProcess;
                                }

                                break;
                            }
                            catch (Exception ex)
                            {
                                ErrorLogAppend(
                                    $"Test task aborted! Fail to process test case: {dM.AllTestCases[TestCaseID].Name}",
                                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                                testTask = TestTask.EmergencyStop;
                                break;
                            }

//--------------------------------------------------------------------------------------------------------------------------------------------------------
                        //處理測試失敗
                        case TestTask.FailProcess:
                            AppendTextToTbLog($"Task: {testTask}, TestCase: {dM.AllTestCases[TestCaseID].Name}");
                            //檢查是否重測
                            int.TryParse(dM.AllTestParams[TestCaseID].RetryMaxCount, out int retry);
                            if (retry == 0)
                            {
                                AppendTextToTbLog($"Test case: {dM.AllTestCases[TestCaseID].Name} - Has no retry!");
                                if (isTimeout)
                                {
                                    isTimeout = false;
                                    UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.TIMEOUT_STR,
                                        System_msg.FAIL_STR, Color.Red);
                                }
                                else
                                {
                                    UpdateLVTest(dM.AllTestCases[TestCaseID].Name, ResultValue, ResultValue, Color.Red);
                                }

                                finalResult = FAIL;
                                testTask = TestTask.KeepTest;
                            }
                            //有重測
                            else
                            {
                                if (Retry_Counter[TestCaseID] < int.Parse(dM.AllTestParams[TestCaseID].RetryMaxCount))
                                {
                                    //重測起始項選擇Reboot
                                    if (dM.AllTestParams[TestCaseID].RetryStartCase == "Reboot")
                                    {
                                        //更新lvTest
                                        UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.RETRY_STR, Color.Red);
                                        Retry_Counter[TestCaseID]++;
                                        Total_Retry_Count++;
                                        testTask = TestTask.Idle;

                                        // 清除listview 顏色與測試內容
                                        if (SystemSetting.clearAfterTest == "1")
                                        {
                                            //清除測試項Before Test Case
                                            for (int index = 0; index < dM.BeforeTestCase.Count; index++)
                                            {
                                                UpdateLVTest(dM.BeforeTestCase[index].Name, string.Empty, string.Empty,
                                                    Color.White);
                                            }

                                            //清除測試項Test Case
                                            for (int index = 0; index < dM.AllTestCases.Count; index++)
                                            {
                                                UpdateLVTest(dM.AllTestCases[index].Name, string.Empty, string.Empty,
                                                    Color.White);
                                            }

                                            //清除測試項After Test Case
                                            for (int index = 0; index < dM.AfterTestCase.Count; index++)
                                            {
                                                UpdateLVTest(dM.AfterTestCase[index].Name, string.Empty, string.Empty,
                                                    Color.White);
                                            }
                                        }

                                        AppendTextToTbLog(
                                            $"Test case: {dM.AllTestCases[TestCaseID].Name} - Has retry! Retry count: {Retry_Counter[TestCaseID]}. Retry from: [Reboot]");
                                        AppendTextToTbLog($"Set delay to {dM.AllTestParams[TestCaseID].Delay}ms.");
                                        Thread.Sleep(int.Parse(dM.AllTestParams[TestCaseID].Delay));
                                    }
                                    //重測起始項選擇某指定項
                                    else
                                    {
                                        //更新lvTest
                                        UpdateLVTest(dM.AllTestCases[TestCaseID].Name, System_msg.RETRY_STR, Color.Red);
                                        Retry_Counter[TestCaseID]++;
                                        Total_Retry_Count++;
                                        int oldCaseID = TestCaseID;
                                        TestCaseID = int.Parse(dM.AllTestParams[TestCaseID].RetryStartCase);
                                        testTask = TestTask.Preprocess;
                                        // 清除listview 顏色與測試內容
                                        if (SystemSetting.clearAfterTest == "1" && TestCaseID > 0)
                                        {
                                            for (int index = 0; index < TestCaseID; index++)
                                            {
                                                UpdateLVTest(dM.AllTestCases[index].Name, string.Empty, string.Empty,
                                                    Color.White);
                                            }
                                        }

                                        AppendTextToTbLog(
                                            $"Test case: {dM.AllTestCases[oldCaseID].Name} - Has retry! Retry count: {Retry_Counter[oldCaseID]}. Retry from: [{dM.AllTestCases[TestCaseID].Name}]");
                                        AppendTextToTbLog($"Set delay to {dM.AllTestParams[oldCaseID].Delay}ms.");
                                        Thread.Sleep(int.Parse(dM.AllTestParams[oldCaseID].Delay));
                                    }
                                }
                                //有重測但超過最大重測
                                else
                                {
                                    //fail
                                    UpdateLVTest(dM.AllTestCases[TestCaseID].Name, ResultValue, System_msg.FAIL_STR,
                                        Color.Red);
                                    finalResult = FAIL;
                                    AppendTextToTbLog(
                                        $"Test case: {dM.AllTestCases[TestCaseID].Name} - Reach maximum retry count: {dM.AllTestParams[TestCaseID].RetryMaxCount} !");
                                    testTask = TestTask.KeepTest;
                                }
                            }

                            break;
//--------------------------------------------------------------------------------------------------------------------------------------------------------
                        case TestTask.KeepTest:
                            AppendTextToTbLog($"Task: {testTask}, TestCase: {dM.AllTestCases[TestCaseID].Name}");
                            if (bool.Parse(SystemSetting.keepTestCheck))
                            {
                                Thread.Sleep(int.Parse(dM.AllTestParams[TestCaseID].Delay));
                                TestCaseID++;
                                if (TestCaseID < dM.AllTestCases.Count)
                                {
                                    AppendTextToTbLog("Keep test check is enabled, process other test......");
                                    testTask = TestTask.Preprocess;
                                }
                                else
                                {
                                    AppendTextToTbLog("Keep test check is enabled, but all test case processed.");
                                    testTask = TestTask.DisplayResult;
                                }
                            }
                            else
                            {
                                AppendTextToTbLog("Keep test check is disabled, finish test......");
                                testTask = TestTask.DisplayResult;
                            }

                            break;
                        case TestTask.DisplayResult:
                            AppendTextToTbLog($"Task: {testTask}");
                            PauseTimer();
                            //display final result
                            finalResult = (finalResult == -1) ? PASS : finalResult;

                            if (finalResult == PASS)
                            {
                                UpdateTbResult(System_msg.PASS_STR, Color.Green);
                                passNum++;
                            }
                            else
                            {
                                UpdateTbResult(System_msg.FAIL_STR, Color.Red);
                                failNum++;
                            }

                            AppendTextToTbLog($"Test result refreshed!");
                            TestCaseID = 0;
                            testTask = TestTask.RunPostSetting;

                            break;
                        case TestTask.RunPostSetting:
                            AppendTextToTbLog(
                                $"Task: {testTask}, Post-TestCase: {dM.AfterTestCase[PostTestCaseID].Name}");
                            isSkip = !Convert.ToBoolean(dM.AfterTestParams[PostTestCaseID].CaseChecked);
                            if (isSkip)
                            {
                                UpdateLVTest(dM.AfterTestCase[PostTestCaseID].Name, System_msg.SKIP_STR,
                                    Color.LightGray);
                                PostTestCaseID++;
                                if (PostTestCaseID >= dM.AfterTestCase.Count)
                                {
                                    PostTestCaseID = 0;
                                    testTask = TestTask.CreateLog;
                                    break;
                                }
                                else
                                {
                                    testTask = TestTask.RunPostSetting;
                                    break;
                                }
                            }

                            if (dM.AfterCommuConfigs[PostTestCaseID].PortID != -1)
                            {
                                serialport = dM.serialPorts[dM.AfterCommuConfigs[PostTestCaseID].PortID];
                                AppendTextToTbLog(
                                    $"Post-TestCase: {dM.AfterTestCase[PostTestCaseID].Name} target serial port: {serialport.PortName}");
                                //Check if Test type is normal device cmd or pass device cmd/fail device cmd
                                if (int.Parse(dM.AfterTestCase[PostTestCaseID].TestType) ==
                                    SystemSetting.TYPE_DEVICE_CMD)
                                {
                                    SendDeviceCommand(serialport, PostTestCaseID, dM.AfterCommuConfigs,
                                        dM.AfterTestCase);
                                    AppendTextToTbLog($"Set delay to {dM.AfterTestParams[PostTestCaseID].Delay}ms.");
                                    Thread.Sleep(int.Parse(dM.AfterTestParams[PostTestCaseID].Delay));
                                    UpdateLVTest(dM.AfterTestCase[PostTestCaseID].Name, System_msg.PROCESSED_STR,
                                        Color.LightGray);
                                }
                                else
                                {
                                    //Check final result
                                    if (finalResult == PASS && int.Parse(dM.AfterTestCase[PostTestCaseID].TestType) ==
                                        SystemSetting.TYPE_PASS_DEVICE_CMD)
                                    {
                                        SendDeviceCommand(serialport, PostTestCaseID, dM.AfterCommuConfigs,
                                            dM.AfterTestCase);
                                        AppendTextToTbLog(
                                            $"Set delay to {dM.AfterTestParams[PostTestCaseID].Delay}ms.");
                                        Thread.Sleep(int.Parse(dM.AfterTestParams[PostTestCaseID].Delay));
                                        //UpdateLVTest((int)POS_lvTestGroup.POS_AFTER_TEST_CASE, PostTestCaseID, System_msg.PROCESSED_STR, Color.LightGray);
                                    }
                                    else if (finalResult == FAIL &&
                                             int.Parse(dM.AfterTestCase[PostTestCaseID].TestType) ==
                                             SystemSetting.TYPE_FAIL_DEVICE_CMD)
                                    {
                                        SendDeviceCommand(serialport, PostTestCaseID, dM.AfterCommuConfigs,
                                            dM.AfterTestCase);
                                        AppendTextToTbLog(
                                            $"Set delay to {dM.AfterTestParams[PostTestCaseID].Delay}ms.");
                                        Thread.Sleep(int.Parse(dM.AfterTestParams[PostTestCaseID].Delay));
                                        UpdateLVTest(dM.AfterTestCase[PostTestCaseID].Name, System_msg.PROCESSED_STR,
                                            Color.LightGray);
                                    }
                                    else
                                    {
                                        UpdateLVTest(dM.AfterTestCase[PostTestCaseID].Name, System_msg.SKIP_STR,
                                            Color.LightGray);
                                        AppendTextToTbLog(
                                            $"Skip Post-TestCase: {dM.AfterTestCase[PostTestCaseID].Name}");
                                    }
                                }
                            }
                            else
                            {
                                if (int.TryParse(dM.AfterTestParams[PostTestCaseID].Delay, out delay))
                                {
                                    AppendTextToTbLog($"Set delay to {dM.AfterTestParams[PostTestCaseID].Delay}ms.");
                                    Thread.Sleep(delay);
                                }
                                //UpdateLVTest((int)POS_lvTestGroup.POS_AFTER_TEST_CASE, PostTestCaseID, System_msg.PROCESSED_STR, Color.LightGray);
                            }

                            PostTestCaseID++;
                            if (PostTestCaseID < dM.AfterTestCase.Count)
                            {
                                testTask = TestTask.RunPostSetting;
                                break;
                            }
                            else
                            {
                                PostTestCaseID = 0;
                                testTask = TestTask.CreateLog;
                                break;
                            }
                        case TestTask.CreateLog:
                            AppendTextToTbLog($"Task: {testTask}");
                            isTesting = false;
                            //更新計數
                            totalNum++;
                            float temp1 = passNum;
                            float temp2 = totalNum;
                            yeild = (float)Math.Round((temp1 / temp2) * 100, 2);

                            //Display Counter
                            DisplayResult();

                            //生成工廠Log
                            CreateFactoryLogFile();
                            //保存數據
                            dM.SaveResultXml(passNum, failNum, totalNum, yeild, SystemSetting.testResultPath);


                            //clear barcode
                            tbBarCode.Invoke(new MethodInvoker(() => { tbBarCode.Text = ""; }));

                            testTask = TestTask.TestEnd;


                            break;
                        case TestTask.EmergencyStop:
                            AppendTextToTbLog($"Task: {testTask}");
                            PauseTimer();
                            UpdateTbResult(System_msg.STOP_STR, Color.Red, string.Empty);

                            isTesting = false;
                            isTimeout = false;
                            IsTestData = false;
                            testTask = TestTask.TestEnd;
                            break;
                        case TestTask.SerialPortError:
                            AppendTextToTbLog($"Task: {testTask}");
                            PauseTimer();
                            UpdateTbResult(System_msg.CHECK_SERIAL_PORT, Color.Red, string.Empty);
                            ShowErrorMsg($"串口發生錯誤");
                            testTask = TestTask.TestEnd;
                            break;
                        case TestTask.TestEnd:
                            AppendTextToTbLog($"Task: {testTask}");
                            PauseTimer();
                            isTesting = false;
                            isTimeout = false;
                            IsTestData = false;
                            Total_Retry_Count = 0;
                            Retry_Counter = new int[dM.AllTestCases.Count];
                            //back to testing
                            Thread.Sleep((int)(decimal.Parse(SystemSetting.delayToWait) * 1000));
                            // 清除listview 顏色與測試內容
                            if (SystemSetting.clearAfterTest == "1")
                            {
                                //清除測試項Before Test Case
                                for (int index = 0; index < dM.BeforeTestCase.Count; index++)
                                {
                                    UpdateLVTest(dM.BeforeTestCase[index].Name, string.Empty, string.Empty, Color.White);
                                }

                                //清除測試項Test Case
                                for (int index = 0; index < dM.AllTestCases.Count; index++)
                                {
                                    UpdateLVTest(dM.AllTestCases[index].Name, string.Empty, string.Empty, Color.White);
                                }

                                //清除測試項After Test Case
                                for (int index = 0; index < dM.AfterTestCase.Count; index++)
                                {
                                    UpdateLVTest(dM.AfterTestCase[index].Name, string.Empty, string.Empty, Color.White);
                                }

                                AppendTextToTbLog($"Listview cleared!");
                            }

                            foreach (var port in dM.serialPorts)
                            {
                                if (SerialPortHandler.PortIsExist(port.PortName))
                                {
                                    UpdateTbResult(System_msg.WAIT_STR, Color.LightBlue);
                                }
                                else
                                {
                                    UpdateTbResult(System_msg.CHECK_SERIAL_PORT, Color.LightBlue);
                                    break;
                                }
                            }


                            AppendTbLogToAppLog();
                            testTask = TestTask.FinishProcess;
                            btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));
                            break;
                    }
                }

                AllowTestStart = true;
            }
            catch (IOException ex)
            {
                // 處理所有其他類型的異常
                isTesting = false;
                isTimeout = false;
                testTask = TestTask.DisplayResult;
                finalResult = FAIL;
                AllowTestStart = true;
                btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));

                UpdateTbResult(System_msg.STOP_STR, Color.Red, string.Empty);
                ErrorLogAppend($"Fail to read data from serial port", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg($"I/O 錯誤 - {ex.Message}");
            }
            catch (Exception ex)
            {
                // 處理所有其他類型的異常
                isTesting = false;
                isTimeout = false;
                testTask = TestTask.DisplayResult;
                finalResult = FAIL;
                AllowTestStart = true;
                btnStart.Invoke(new MethodInvoker(() => { btnStart.Enabled = true; }));

                UpdateTbResult(System_msg.STOP_STR, Color.Red, string.Empty);
                ErrorLogAppend($"Fail to process test case", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg($"測試時發生錯誤: {ex.Message} {ex.StackTrace}");
            }
        }
        private double CalculateAvg(double[] value)
        {
            if (int.TryParse(SystemSetting.averageSetting, out int avgType))
            {
                switch (avgType)
                {
                    case (int)AvgType.Default:
                        return DataCalculator.GetAverage(value);
                    case (int)AvgType.Median:
                        return DataCalculator.GetAverageByMedian(value);
                    case (int)AvgType.Deviation:
                        return DataCalculator.GetAverageByDeviation(value);
                    case (int)AvgType.IQR:
                        return DataCalculator.GetAverageByIQR(value);
                    default:
                        return DataCalculator.GetAverage(value);
                }
            }
            else
            {
                return DataCalculator.GetAverage(value);
            }
        }
        
        /// <summary>
        /// 测试结束,拿到设备回传数据执行
        /// </summary>
        private void TaskProcessed()
        {
            if (IsPass) // 测试通过
            {
                AppendTextToTbLog($"Test case: {dM.AllTestCases[TestCaseID].Name} PASS! Result: {ResultValue}");
                UpdateLVTest(dM.AllTestCases[TestCaseID].Name, ResultValue, System_msg.PASS_STR, Color.Green);

                IsPass = false;
                Thread.Sleep(int.Parse(dM.AllTestParams[TestCaseID].Delay));
                TestCaseID++;
                if (TestCaseID < dM.AllTestCases.Count)
                {
                    testTask = TestTask.Preprocess;
                }
                else
                {
                    testTask = TestTask.DisplayResult;
                }
            }
            else // 测试失败
            {
                AppendTextToTbLog($"Test case: {dM.AllTestCases[TestCaseID].Name} FAIL! Result: {ResultValue}");
                UpdateLVTest(dM.AllTestCases[TestCaseID].Name, ResultValue, System_msg.FAIL_STR, Color.Red);

                testTask = TestTask.FailProcess;
            }
        }

        #endregion

        #region Log

        /// <summary>
        /// 生成工厂log文件
        /// </summary>
        private void CreateFactoryLogFile()
        {
            try
            {
                string result = tbResult.Text;
                //set log filename 日志文件名 : 当前时间-条形码-机器id-app名称-结果-app版本
                string factoryLogFilename = $"{DateTime.Now.ToString("yyyyMMddHHmmss")}" +
                                            "-"+
                                            $"{SystemSetting.BarCodeFilename}" +
                                            "-"+
                                            $"{SystemSetting.machineId}" +
                                            "-"+
                                            $"{SystemSetting.appName}" +
                                            "-"+
                                            $"{result}" +
                                            "-"+
                                            $"{SystemSetting.appVersion}.txt";

                char simpleResult = tbResult.Text[0]; //抓第一個字元 -> "P"ASS / "F"AIL

                //Log information on first line
                // 日志文件首行信息 : 结果,机器id,总数,app名称,条形码,当前时间(年月日),当前时间(时分秒),简易结果(P/F),重试次数,班别,机器id,结果详情,多版数,多版结果,多版重测,app版本,制程编号,治具编号,工单号,
                string logInformation = $"{result}," +
                                        $"-{SystemSetting.machineId}-," +
                                        $"{totalNum}," +
                                        $"{SystemSetting.appName}," +
                                        $"{SystemSetting.barCode}," +
                                        $"{DateTime.Now.ToString("yyyyMMdd")}," +
                                        $"{DateTime.Now.ToString("HHmmss")}," +
                                        $"{simpleResult}," +
                                        $"{Total_Retry_Count}," +
                                        $"{SystemSetting.workShift}," +
                                        $"{SystemSetting.workId}," +
                                        $"{SystemSetting.resultDetail}," +
                                        $"{SystemSetting.boardNum}," +
                                        $"{SystemSetting.multiBoardResult}," +
                                        $"{SystemSetting.multiBoardRetry}," +
                                        $"{SystemSetting.appVersion}," +
                                        $"{SystemSetting.processOrder}," +
                                        $"{SystemSetting.jigId}," +
                                        $"{SystemSetting.workOrder}" +
                                        $",";

                //Combine other log into factory tracking system log here 换两行 : 预留信息填写
                string LogfileContent = $"{Environment.NewLine}{Environment.NewLine}";

                //測項規格(欄位之間使用“，”隔開)
                //0: Item
                //1: Description
                //2: Test Point 
                //3: Min
                //4: Nominal Value
                //5: Max
                //6: Unit
                //7: Test Data
                //8: Test Result
                int logItemId = 0;
                for (int index = 0; index < lvTest.Items.Count; index++)
                {
                    string[] logContent = new string[9];
                    bool isChecked = false;
                    lvTest.Invoke(new MethodInvoker(() => { isChecked = lvTest.Items[index].Checked; }));
                    if (!isChecked)
                    {
                        continue;
                    }

                    lvTest.Invoke(new MethodInvoker(() =>
                    {
                        for (int i = 0; i < 9; i++)
                        {
                            logContent[i] = lvTest.Items[index].SubItems[i].Text;
                        }
                    }));
                    logContent[0] = logItemId.ToString();
                    logItemId++;
                    LogfileContent += string.Join(",", logContent.Take(9)) + Environment.NewLine;
                }

                logItemId = 0;
                logInformation += LogfileContent;

                //create Factory Log file 
                File.WriteAllText($"{SystemSetting.factoryLogPath}/{factoryLogFilename}", logInformation);

                //Display message
                AppendTextToTbLog(
                    $"Factory log file：{factoryLogFilename} created, under path: {SystemSetting.factoryLogPath}");
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to create factory log file", ex.StackTrace, SystemState.ErrorHandling);
            }
        }

        /// <summary>
        /// 创建app日志文件,写入第一行头文件
        /// </summary>
        private void CreateAppLogFile()
        {
            string filepath = Path.Combine(SystemSetting.defaultAppLogPath, SystemSetting.appLogFilename);
            string Header = $"{SystemSetting.SOFTWARE_NAME} - {SystemSetting.SOFTWARE_VERSION} Application Log File." +
                            Environment.NewLine + Environment.NewLine;
            using (StreamWriter sw = new StreamWriter(filepath, true))
            {
                sw.WriteLine(Header);
            }
        }

        /// <summary>
        /// app日志输出指定路径
        /// </summary>
        /// <param name="logUpdate"></param>
        public void AppLogger(string logUpdate)
        {
            string path = Path.Combine(SystemSetting.appLogPath, SystemSetting.appLogFilename);
            using (StreamWriter sw = new StreamWriter(path, true))
            {
                sw.WriteLine(logUpdate);
            }
            //AppendTextToLog($"Log written: {logUpdate}");
        }

        /// <summary>
        /// info级别的日志
        /// </summary>
        /// <param name="logDetail"></param>
        /// <param name="systemStatus"></param>
        public static void InfoLogAppend(string logDetail, SystemState systemStatus)
        {
            logger.LogDetail = logDetail;
            logger.SystemStatus = systemStatus;
        }

        /// <summary>
        /// Error级别的日志
        /// </summary>
        /// <param name="logDetail"></param>
        /// <param name="errorMsg"></param>
        /// <param name="systemStatus"></param>
        public static void ErrorLogAppend(string logDetail, string errorMsg, SystemState systemStatus)
        {
            logger.MsgType = System_msg.LOG_ERROR;
            logger.LogDetail = logDetail;
            logger.ErrorMsg = errorMsg;
            logger.SystemStatus = systemStatus;
        }

        /// <summary>
        /// Warn级别的日志
        /// </summary>
        /// <param name="logDetail"></param>
        /// <param name="systemStatus"></param>
        public static void WarnLogAppend(string logDetail, SystemState systemStatus)
        {
            logger.MsgType = System_msg.LOG_WARNING;
            logger.LogDetail = logDetail;
            logger.SystemStatus = systemStatus;
        }

        private void AppendTbLogToAppLog()
        {
            string testLog = "";
            tbLog.Invoke(new MethodInvoker(() => { testLog = tbLog.Text; }));
            InfoLogAppend($"Test log: "
                          + Environment.NewLine
                          + $"{System_msg.LOG_SEPARATOR}"
                          + Environment.NewLine
                          + $"{testLog}", SystemState.TestTaskFinish);
        }

        #endregion

        #region SerialPorts

        public byte[] GetUartCommand(byte function, byte status, byte value1, byte value2)
        {
            Array.Clear(SendUART, 0, UartSetting.UART_TX_FRAME_NUM);
            SendUART[UartSetting.POS_HEADER0] = UartSetting.UART_TX_HEADER0;
            SendUART[UartSetting.POS_HEADER1] = UartSetting.UART_TX_HEADER1;
            SendUART[UartSetting.POS_FUNCTION] = function;
            SendUART[UartSetting.POS_ORDER] = status;
            SendUART[UartSetting.POS_VALUE0] = value1;
            SendUART[UartSetting.POS_VALUE1] = value2;
            SendUART[UartSetting.POS_TAIL0] = UartSetting.UART_TAIL0;
            SendUART[UartSetting.POS_TAIL1] = UartSetting.UART_TAIL1;
            return SendUART;
        }

        /// <summary>
        /// 生成Modbus报文
        /// </summary>
        /// <param name="function">功能码</param>
        /// <param name="addr">从站地址</param>
        /// <param name="register"></param>
        /// <param name="value"></param>
        /// <param name="serialPort"></param>
        /// <returns></returns>
        public byte[] GetModbusCommand(byte function, byte addr, ushort register, ushort value, SerialPort serialPort)
        {
            checksumCalculator = new ChecksumCalculator();
            Array.Clear(SendModbus, 0, ModbusSetting.MODBUS_TX_FRAME_LEN);
            SendModbus[ModbusSetting.POS_SLAVE_ADDRESS] = addr;
            SendModbus[ModbusSetting.POS_FUNCTION] = function;
            SendModbus[ModbusSetting.POS_REGISTER0] = (byte)(register >> 8);
            SendModbus[ModbusSetting.POS_REGISTER1] = (byte)(register & 0xFF);
            SendModbus[ModbusSetting.POS_NUM_OF_REG0] = (byte)(value >> 8);
            SendModbus[ModbusSetting.POS_NUM_OF_REG1] = (byte)(value & 0xFF);
            byte[] crc =
                checksumCalculator.CalculateChecksum(SendModbus, ModbusSetting.MODBUS_TX_FRAME_LEN - 2, serialPort);
            SendModbus[ModbusSetting.POS_CRC0] = crc[1];
            SendModbus[ModbusSetting.POS_CRC1] = crc[0];
            return SendModbus;
        }

        public byte[] GetCustomCommand(string customData)
        {
            string[] data = customData.Split(' ');
            SendCustom = new byte[data.Length];
            for (int i = 0; i < data.Length; i++)
            {
                SendCustom[i] = Convert.ToByte(data[i], 16);
            }

            return SendCustom;
        }

        public volatile bool isProcessingData = false;

        /// <summary>
        /// 串口接受到数据触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort serial = (SerialPort)sender;

            hasSync = SerialPortConfigForm.SyncStateDict[serial.PortName];
            string syncRaw;
            byte[] syncData = new byte[0];

            try
            {
                while (serial.IsOpen && serial.BytesToRead > 0)
                {
                    isProcessingData = true;
                    byte receivedByte = (byte)serial.ReadByte();
                    if (hasSync)
                    {
                        syncRaw = SerialPortConfigForm.SyncFrameDict[serial.PortName];
                        syncData = (String.IsNullOrEmpty(syncRaw))
                            ? new byte[0]
                            : (syncRaw.Split(' ')).Select(x => Convert.ToByte(x, 16)).ToArray();
                        if (!isSynchronized)
                        {
                            InfoLogAppend($"Sync data: {receivedByte} received from: {serial.PortName}",
                                SystemState.Communicate);
                            HandleSyncData(serial, receivedByte, syncData);
                        }
                        else
                        {
                            HandleNormalData(serial, receivedByte, syncData);
                        }
                    }
                    else
                    {
                        HandleNormalData(serial, receivedByte, syncData);
                    }

                    isProcessingData = false;
                }
            }
            catch (Exception ex)
            {
                serial = (SerialPort)sender;
                // 處理所有錯誤串口訊息類型的異常
                if (isTesting)
                {
                    isTesting = false;
                    isTimeout = false;
                    testTask = TestTask.DisplayResult;
                    finalResult = FAIL;
                }

                UpdateTbResult(System_msg.CHECK_SERIAL_PORT, Color.Red, string.Empty);
                isSynchronized = false;
                //dM.CloseAllSerialPort();
                ErrorLogAppend($"Fail to read data from serial port: {serial.PortName}", ex.Message + ex.StackTrace,
                    SystemState.ErrorHandling);
                ShowErrorMsg($"串口接收錯誤 - {ex.Message}");
            }
        }

        /// <summary>
        /// 异步处理标准数据
        /// </summary>
        /// <param name="port"></param>
        /// <param name="receivedByte"></param>
        /// <param name="syncData"></param>
        private async void HandleNormalData(SerialPort port, byte receivedByte, byte[] syncData)
        {
            spRxBuffer[port.PortName].Add(receivedByte);
            //Check if is sync data
            if (hasSync && isSyncData(port, spRxBuffer[port.PortName], syncData))
            {
                InfoLogAppend(
                    $"Sync data received from: {port.PortName}, buffer length: {spRxBuffer[port.PortName].Count}",
                    SystemState.CommunicateSuccess);

                //Skip sync data
                spRxBuffer[port.PortName].Clear();
                return;
            }

            if (spRxBuffer[port.PortName].Count == SerialPortConfigForm.DefaultBufferSizeDict[port.PortName])
            {
                //Check if has crc check
                for (int i = 0; i < dM.SerialPortConfigs.Count; i++)
                {
                    if (dM.SerialPortConfigs[i].PortName == port.PortName &&
                        bool.Parse(dM.SerialPortConfigs[i].CRCCheck))
                    {
                        if (DataCalculator.CRCCheck(spRxBuffer[port.PortName].ToArray()))
                        {
                            InfoLogAppend(
                                $"CRC check passed: {BitConverter.ToString(spRxBuffer[port.PortName].ToArray()).Replace("-", " ")}",
                                SystemState.CommunicateSuccess);
                            byte[] testData = spRxBuffer[port.PortName].ToArray();
                            byte[] sysData = spRxBuffer[port.PortName].ToArray();
                            byte[] chartData = spRxBuffer[port.PortName].ToArray();

                            spRxBuffer[port.PortName].Clear();

                            if (IsTestData)
                            {
                                await Task.WhenAll(HandleTestData(port, testData), HandleSystemData(port, sysData),
                                    HandleChartData(port, chartData));
                            }
                            else
                            {
                                await Task.WhenAll(HandleSystemData(port, sysData), HandleChartData(port, chartData));
                            }
                        }
                        else
                        {
                            WarnLogAppend(
                                $"CRC check failed: {BitConverter.ToString(spRxBuffer[port.PortName].ToArray()).Replace("-", " ")}",
                                SystemState.WarningHandling);
                            //移除第一個元素
                            spRxBuffer[port.PortName].RemoveAt(0);
                        }
                    }
                    else if (dM.SerialPortConfigs[i].PortName == port.PortName &&
                             !bool.Parse(dM.SerialPortConfigs[i].CRCCheck))
                    {
                        byte[] testData = spRxBuffer[port.PortName].ToArray();
                        byte[] sysData = spRxBuffer[port.PortName].ToArray();
                        byte[] chartData = spRxBuffer[port.PortName].ToArray();

                        spRxBuffer[port.PortName].Clear();

                        if (IsTestData)
                        {
                            await Task.WhenAll(HandleTestData(port, testData), HandleSystemData(port, sysData),
                                HandleChartData(port, chartData));
                        }
                        else
                        {
                            await Task.WhenAll(HandleSystemData(port, sysData), HandleChartData(port, chartData));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 异步处理系统数据
        /// </summary>
        /// <param name="port"></param>
        /// <param name="received"></param>
        private async Task HandleSystemData(SerialPort port, byte[] received)
        {
            await Task.Run(() =>
            {
                byte[] rxData = received;
                //Check if is device cmd trigger data
                if (SystemSetting.askDeviceReturnPort == port.PortName && bool.Parse(SystemSetting.deviceCMDCheck) &&
                    AllowTestStart)
                {
                    if (IsTestData)
                    {
                        WarnLogAppend("Device cmd trigger data received while testing!", SystemState.WarningHandling);
                        return;
                    }

                    //Validate device cmd trigger data
                    string[] cmdAllStr = SystemSetting.askDeviceReturnCMD.Split(',');
                    for (int i = 0; i < cmdAllStr.Length; i++)
                    {
                        string[] cmdStr = cmdAllStr[i].Split(' ');
                        byte[] cmdData = new byte[cmdStr.Length];
                        cmdData = Array.ConvertAll(cmdStr, x => Convert.ToByte(x, 16));
                        if (rxData.Length == cmdData.Length)
                        {
                            int j = 0;
                            while (j < cmdData.Length)
                            {
                                if (rxData[j] != cmdData[j])
                                {
                                    break;
                                }

                                j++;
                            }

                            if (j == cmdData.Length)
                            {
                                InfoLogAppend(
                                    $"Device cmd trigger data received: {BitConverter.ToString(rxData).Replace("-", " ")}, via {port.PortName}",
                                    SystemState.TestTaskInitial);
                                RunStartTest();
                                break;
                            }
                            else
                            {
                                WarnLogAppend(
                                    $"Device cmd trigger data received: {BitConverter.ToString(rxData).Replace("-", " ")} is not match to: {BitConverter.ToString(cmdData).Replace("-", " ")}!",
                                    SystemState.WarningHandling);
                            }
                        }
                    }
                }
            });
        }

        /// <summary>
        /// 异步处理测试数据
        /// </summary>
        /// <param name="port"></param>
        /// <param name="received"></param>
        private async Task HandleTestData(SerialPort port, byte[] received)
        {
            if (IsTestData)
            {
                await Task.Run(() =>
                {
                    byte[] rxData = received;
                    //Custom return frame
                    if ((int.Parse(dM.AllCommuConfigs[TestCaseID].RxSetting) == (int)txrxSetting.CustomTxRx))
                    {
                        int size = SerialPortConfigForm.DefaultBufferSizeDict[port.PortName];
                        if (dM.AllCommuConfigs[TestCaseID].RxFrameLength != "N/A")
                        {
                            size = int.Parse(dM.AllCommuConfigs[TestCaseID].RxFrameLength);
                        }

                        if (rxData.Length != size)
                        {
                            return;
                        }

                        string[] header = dM.AllCommuConfigs[TestCaseID].RxFrameHeader.Split(' ');
                        int headerLength = header.Length;
                        int i = 0;
                        while (i < headerLength)
                        {
                            if (rxData[i] != Convert.ToByte(header[i], 16))
                            {
                                WarnLogAppend(
                                    $"Received data header is not match: {BitConverter.ToString(rxData).Replace("-", " ")}!",
                                    SystemState.WarningHandling);
                                break;
                            }

                            i++;
                        }

                        if (i == headerLength)
                        {
                            TestResultData = new byte[rxData.Length];
                            TestResultData = rxData;
                            serialBufferReady[port.PortName] = true;
                            IsTestData = false;
                            IsModbus = false;
                        }
                    }
                    else if ((int.Parse(dM.AllCommuConfigs[TestCaseID].RxSetting) == (int)txrxSetting.NoTxRx))
                    {
                        return;
                    }
                    else
                    {
                        //Defualt Uart frame
                        if ((int.Parse(dM.AllTestCases[TestCaseID].TestParamType) == (int)CaseCommuType.Default &&
                             rxData.Length == UartSetting.UART_RX_FRAME_NUM &&
                             CheckHeadTail(rxData)))
                        {
                            //Emgergency stop checked
                            if (rxData[UartSetting.POS_FUNCTION] == UartFunction.CMD_EMERGENCY_STOP)
                            {
                                testTask = TestTask.EmergencyStop;
                                return;
                            }
                            //Test start command send by device checked
                            else if (rxData[UartSetting.POS_FUNCTION] == UartFunction.CMD_START_STOP &&
                                     rxData[UartSetting.POS_ORDER] == UartValue.PARM_TEST_START)
                            {
                                if (!bool.Parse(SystemSetting.askDeviceCheck))
                                {
                                    WarnLogAppend(
                                        "Test start command received, but device CMD trigger mode is disabled!",
                                        SystemState.WarningHandling);
                                    return;
                                }

                                if (!AllowTestStart)
                                {
                                    WarnLogAppend("Test start command received, but test is not allowed to start!",
                                        SystemState.WarningHandling);
                                    return;
                                }

                                InfoLogAppend(
                                    $"Test start command received: {BitConverter.ToString(rxData).Replace("-", " ")}, via {port.PortName}",
                                    SystemState.TestTaskInitial);
                                RunStartTest();
                                return;
                            }
                            //Default frame
                            else
                            {
                                TestResultData = new byte[rxData.Length];
                                TestResultData = rxData;
                                //SaveReceivedData(TestResultData, rxData);
                                serialBufferReady[port.PortName] = true;
                                IsTestData = false;
                                IsModbus = false;
                                return;
                            }
                        }

                        //Default Modbus frame
                        else if ((int.Parse(dM.AllTestCases[TestCaseID].TestParamType) == (int)CaseCommuType.Modbus &&
                                  rxData.Length == ModbusSetting.MODBUS_RX_FRAME_LEN &&
                                  rxData[ModbusSetting.POS_SLAVE_ADDRESS] ==
                                  Convert.ToByte(dM.AllCommuConfigs[TestCaseID].SlaveID, 16)))
                        {
                            TestResultData = new byte[rxData.Length];
                            TestResultData = rxData;
                            //SaveReceivedData(TestResultData, rxData);
                            serialBufferReady[port.PortName] = true;
                            IsTestData = false;
                            IsModbus = true;
                            spRxBuffer[port.PortName].Clear();
                        }
                    }
                });
            }
        }

        /// <summary>
        /// 异步处理图表数据
        /// </summary>
        /// <param name="port"></param>
        /// <param name="received"></param>
        private async Task HandleChartData(SerialPort port, byte[] received)
        {
            byte[] rxData = received;
            if (visualizerBuffers.Count > 0)
            {
                if (rxData.Length == SerialPortConfigForm.DefaultBufferSizeDict[port.PortName])
                {
                    try
                    {
                        Dictionary<string, int> chartData = await GetChartData(port.PortName, rxData);

                        if (chartData == null || chartData.Count == 0)
                        {
                            return;
                        }

                        foreach (KeyValuePair<string, int> pair in chartData)
                        {
                            await Task.Run(() =>
                            {
                                string title = pair.Key;
                                int data = pair.Value;
                                if (visualizerForms.ContainsKey(title) && IsFormOpen(visualizerForms[title]))
                                {
                                    BlockingCollection<double> buffer = visualizerBuffers[title];
                                    if (!buffer.IsAddingCompleted)
                                    {
                                        buffer.Add(data); // 將數據放入 BlockingCollection
                                    }
                                }
                            });
                        }
                    }
                    catch (ObjectDisposedException ex)
                    {
                        ErrorLogAppend($"Fail to update chart ", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                    }
                    catch (Exception ex)
                    {
                        ErrorLogAppend($"Fail to update chart ", ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                    }
                }
            }
        }

        /// <summary>
        /// 处理异步数据
        /// </summary>
        /// <param name="port"></param>
        /// <param name="receivedByte"></param>
        /// <param name="syncData"></param>
        private void HandleSyncData(SerialPort port, byte receivedByte, byte[] syncData)
        {
            try
            {
                if (receivedByte == syncData[syncIndex])
                {
                    syncIndex++;
                    if (syncIndex == syncData.Length)
                    {
                        isSynchronized = true;
                        syncIndex = 0;
                        InfoLogAppend($"Sync data received from {port.PortName}", SystemState.CommunicateSuccess);
                    }
                }
                else
                {
                    syncIndex = 0; // 同步失敗，重新開始比對
                }
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to handle sync data from serial port: {port.PortName}",
                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg($"串口同步錯誤:{port.PortName} - {ex.Message}");
            }
        }

        private bool isSyncData(SerialPort port, List<byte> buffer, byte[] data)
        {
            if (buffer.Count == data.Length)
            {
                for (int i = 0; i < data.Length; i++)
                {
                    if (buffer[i] != data[i])
                    {
                        return false;
                    }
                }

                return true;
            }

            return false;
        }

        private void SaveReceivedData(byte[] targetBuffer, byte[] sourceBuffer)
        {
            Array.Copy(sourceBuffer, targetBuffer, sourceBuffer.Length);
        }

        private bool CheckHeadTail(byte[] buffer)
        {
            if (buffer[UartSetting.POS_HEADER0] == UartSetting.UART_RX_HEADER0 &&
                buffer[UartSetting.POS_HEADER1] == UartSetting.UART_RX_HEADER1 &&
                buffer[UartSetting.POS_TAIL0] == UartSetting.UART_TAIL0 &&
                buffer[UartSetting.POS_TAIL1] == UartSetting.UART_TAIL1)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 发送设备命令
        /// </summary>
        /// <param name="serialPort">串口</param>
        /// <param name="caseID">测试项id</param>
        /// <param name="commuConfigs">通讯配置</param>
        /// <param name="testCases">测试项</param>
        private void SendDeviceCommand(SerialPort serialPort, int caseID, List<CommuConfig> commuConfigs,
            List<TestCase> testCases)
        {
            InfoLogAppend($"Try to send device command through {serialPort.PortName}, case: {testCases[caseID].Name}",
                SystemState.Communicate);
            try
            {
                //檢查傳輸協議模式
                switch (int.Parse(testCases[caseID].CommuType))
                {
                    case (int)CaseCommuType.Default:
                        //檢查是否為自定義封包
                        if (int.Parse(commuConfigs[caseID].TxSetting) == (int)txrxSetting.CustomTxRx)
                        {
                            //custom frame
                            this.tempUartTx = GetCustomCommand(commuConfigs[caseID].CustomTxFrame);
                            serialPort.Write(this.tempUartTx, 0, this.tempUartTx.Length);
                            AppendTextToTbLog($"Send command through: {serialPort.PortName} -- {BitConverter.ToString(this.tempUartTx).Replace("-", " ")}");
                        }
                        else if (int.Parse(commuConfigs[caseID].TxSetting) == (int)txrxSetting.DefaultTxRx)
                        {
                            //default frame
                            this.tempUartTx = GetUartCommand(UartFunction.CMD_TEST_EXE, (byte)caseID,
                                UartValue.DONT_CARE, UartValue.DONT_CARE);
                            serialPort.Write(this.tempUartTx, 0, this.tempUartTx.Length);
                            AppendTextToTbLog($"Send command through: {serialPort.PortName} -- {BitConverter.ToString(this.tempUartTx).Replace("-", " ")}");
                        }
                        else
                        {
                            AppendTextToTbLog($"Test case: {testCases[caseID].Name} no need to send test command.");
                        }

                        break;

                    case (int)CaseCommuType.Modbus:
                        //檢查是否為自定義封包
                        if (int.Parse(commuConfigs[caseID].TxSetting) == (int)txrxSetting.CustomTxRx)
                        {
                            //custom frame
                            this.tempModbusTx = GetCustomCommand(commuConfigs[caseID].CustomTxFrame);
                            serialPort.Write(this.tempUartTx, 0, this.tempUartTx.Length);
                            AppendTextToTbLog(
                                $"Send command through: {serialPort.PortName} -- {BitConverter.ToString(this.tempModbusTx).Replace("-", " ")}");
                        }
                        else if (int.Parse(commuConfigs[caseID].TxSetting) == (int)txrxSetting.DefaultTxRx)
                        {
                            //default Modbus RTU frame
                            this.tempModbusTx = GetModbusCommand(ModbusFunction.CMD_READ_MULTI_REGISTER,
                                Convert.ToByte(commuConfigs[caseID].SlaveID, 16),
                                Convert.ToUInt16(commuConfigs[caseID].RegAddress, 16),
                                Convert.ToUInt16(commuConfigs[caseID].NumOfRegister, 16),
                                serialPort);
                            serialPort.Write(this.tempModbusTx, 0, this.tempModbusTx.Length);
                            AppendTextToTbLog(
                                $"Send command through: {serialPort.PortName} -- {BitConverter.ToString(this.tempModbusTx).Replace("-", " ")}");
                        }
                        else
                        {
                            AppendTextToTbLog($"Test case: {testCases[caseID].Name} no need to send test command.");
                        }

                        break;
                    case (int)CaseCommuType.NoRxTx:
                        AppendTextToTbLog($"Test case: {testCases[caseID].Name} no need to send test command.");
                        break;
                }

                InfoLogAppend($"Test execute command sent through {serialPort.PortName}", SystemState.Communicate);
            }
            catch (Exception ex)
            {
                ErrorLogAppend($"Fail to send test execute command through {serialPort.PortName}",
                    ex.Message + ex.StackTrace, SystemState.ErrorHandling);
                ShowErrorMsg($"Fail to send test execute command through {serialPort.PortName} - {ex.Message}");
            }
        }

        /// <summary>
        /// 根据id获取端口号
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        private string GetPortNameByID(int id)
        {
            string name = "";
            for (int i = 0; i < dM.SerialPortConfigs.Count; i++)
            {
                if (dM.SerialPortConfigs[i].PortID == id)
                {
                    name = dM.SerialPortConfigs[i].PortName;
                    break;
                }
            }

            return name;
        }

        #endregion

        #region Logic Check

        private void CheckLogicLevel(byte value0, int caseID)
        {
            //0=LOW, 1=HIGH
            if (value0 == 0)
            {
                ResultValue = "LOW";
                if (dM.AllTestParams[caseID].NormalValue == ResultValue)
                {
                    IsPass = true;
                    TaskProcessed();
                }
                else
                {
                    IsPass = false;
                    TaskProcessed();
                }
            }
            else if (value0 == 1)
            {
                ResultValue = "HIGH";
                if (dM.AllTestParams[caseID].NormalValue == ResultValue)
                {
                    IsPass = true;
                    TaskProcessed();
                }
                else
                {
                    IsPass = false;
                    TaskProcessed();
                }
            }
        }

        /// <summary>
        /// 检查Modbus返回报文帧数据
        /// </summary>
        /// <param name="value0"></param>
        /// <param name="value1"></param>
        /// <param name="caseID"></param>
        private void CheckFloatData(byte value0, byte value1, int caseID)
        {
            float floatValue = 0.0f;
            //計算讀取值

            ushort combinedValue = value0;
            combinedValue = (ushort)((combinedValue << 8) | value1);
            // 計算浮點數
            floatValue = combinedValue * float.Parse(dM.AllTestParams[caseID].DataResolution);

            //判斷是否超出上下限
            float.TryParse(dM.AllTestParams[caseID].UpperLimit, out float tmpUpperLimit);
            float.TryParse(dM.AllTestParams[caseID].LowerLimit, out float tmpLowerLimit);
            ResultValue = floatValue.ToString();
            if (floatValue <= tmpUpperLimit && floatValue >= tmpLowerLimit)
            {
                IsPass = true;
                TaskProcessed();
            }
            else
            {
                IsPass = false;
                TaskProcessed();
            }
        }

        #endregion

        #region Average Method

        private void menuDefaultAvg_Click(object sender, EventArgs e)
        {
            SystemSetting.averageSetting = ((int)AvgType.Default).ToString();
            SystemSetting.AVERAGE_STR = "Default";
            dM.UpdateINISetting(SystemSetting.INI_SECTION_AVERAGE_SETTING, SystemSetting.AVERAGE_SETTING,
                SystemSetting.averageSetting);
            menuMedian.Checked = false;
            menuIQR.Checked = false;
            menuDeviation.Checked = false;
        }

        private void menuMedian_Click(object sender, EventArgs e)
        {
            SystemSetting.averageSetting = ((int)AvgType.Median).ToString();
            SystemSetting.AVERAGE_STR = "Median";
            dM.UpdateINISetting(SystemSetting.INI_SECTION_AVERAGE_SETTING, SystemSetting.AVERAGE_SETTING,
                SystemSetting.averageSetting);
            menuDefaultAvg.Checked = false;
            menuIQR.Checked = false;
            menuDeviation.Checked = false;
        }

        private void menuIQR_Click(object sender, EventArgs e)
        {
            SystemSetting.averageSetting = ((int)AvgType.IQR).ToString();
            SystemSetting.AVERAGE_STR = "IQR";
            dM.UpdateINISetting(SystemSetting.INI_SECTION_AVERAGE_SETTING, SystemSetting.AVERAGE_SETTING,
                SystemSetting.averageSetting);
            menuDefaultAvg.Checked = false;
            menuMedian.Checked = false;
            menuDeviation.Checked = false;
        }

        private void menuDeviation_Click(object sender, EventArgs e)
        {
            SystemSetting.averageSetting = ((int)AvgType.Deviation).ToString();
            SystemSetting.AVERAGE_STR = "Deviation";
            dM.UpdateINISetting(SystemSetting.INI_SECTION_AVERAGE_SETTING, SystemSetting.AVERAGE_SETTING,
                SystemSetting.averageSetting);
            menuDefaultAvg.Checked = false;
            menuMedian.Checked = false;
            menuIQR.Checked = false;
        }

        #endregion

        /// <summary>
        /// 当用户点击复选框的时候，就会触发ItemChecked事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void lvTest_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            lvTest.ItemChecked -= lvTest_ItemChecked;
            if (e.Item.Checked)
            {
                // 选中
                string caseGroupName = "";
                string itemName = e.Item.SubItems[(int)POS_ListViewSub.NAME_POS].Text;
                for (int i = 0; i < dM.AllTestCases.Count; i++)
                {
                    if (dM.AllTestCases[i].Name == itemName)
                    {
                        dM.AllTestParams[i].CaseChecked = 1;
                        caseGroupName = dM.AllTestParams[i].CaseGroup;
                        break;
                    }
                }

                //更新Group的Checked狀態
                UpdateGroupChecked(1, caseGroupName);
                dM.RefreshListViewCases();
            }
            else
            {
                string caseGroupName = "";
                string itemName = e.Item.SubItems[(int)POS_ListViewSub.NAME_POS].Text;
                for (int i = 0; i < dM.AllTestCases.Count; i++)
                {
                    if (dM.AllTestCases[i].Name == itemName)
                    {
                        dM.AllTestParams[i].CaseChecked = 0;
                        caseGroupName = dM.AllTestParams[i].CaseGroup;
                        break;
                    }
                }

                UpdateGroupChecked(0, caseGroupName);
                dM.RefreshListViewCases();
            }
            lvTest.ItemChecked += lvTest_ItemChecked;
        }

        /// <summary>
        /// 更新Group的Checked狀態
        /// </summary>
        /// <param name="check"></param>
        /// <param name="caseGroupName"></param>
        private async void UpdateGroupChecked(int check, string caseGroupName)
        {
            if (caseGroupName == "")
            {
                return;
            }
            else
            {
                await Task.WhenAll(
                    Task.Run(() =>
                    {
                        for (int i = 0; i < dM.BeforeTestParams.Count; i++)
                        {
                            if (dM.BeforeTestParams[i].CaseGroup == caseGroupName)
                            {
                                dM.BeforeTestParams[i].CaseChecked = check;
                            }
                        }
                    }),
                    Task.Run(() =>
                    {
                        for (int i = 0; i < dM.AllTestParams.Count; i++)
                        {
                            if (dM.AllTestParams[i].CaseGroup == caseGroupName)
                            {
                                dM.AllTestParams[i].CaseChecked = check;
                            }
                        }
                    }),
                    Task.Run(() =>
                    {
                        for (int i = 0; i < dM.AfterTestParams.Count; i++)
                        {
                            if (dM.AfterTestParams[i].CaseGroup == caseGroupName)
                            {
                                dM.AfterTestParams[i].CaseChecked = check;
                            }
                        }
                    })
                );
            }
        }


        #region Deprecated

        private void menuImportTestResult_Click(object sender, EventArgs e)
        {
            if (SystemSetting.isEngineer || SystemSetting.isAdmin)
            {
                string title = "Open Test Result File";
                string filter = "Xml Files (*.xml)|*.xml";
                string filepath = OpenSaveDialog(title, filter);
                if (!string.IsNullOrEmpty(filepath))
                {
                    dM.LoadCounterInfo(filepath);
                    DisplayResult();
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }
        }

        private void menuImportUserInfo_Click(object sender, EventArgs e)
        {
            if (SystemSetting.isAdmin)
            {
                string title = "Open User Info File";
                string filter = "Xml Files (*.xml)|*.xml";
                string filepath = OpenSaveDialog(title, filter);
                if (!string.IsNullOrEmpty(filepath))
                {
                    bool loadUser = dM.LoadUserFile(filepath);
                    if (loadUser)
                    {
                        ShowInfoMsg("成功匯入使用者資料！");
                    }
                    else
                    {
                        ShowErrorMsg("匯入使用者資料失敗！");
                    }
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }
        }

        private void menuExportTestResult_Click(object sender, EventArgs e)
        {
            if (SystemSetting.isAdmin)
            {
                string path = FileHandler.GetFileName("Xml Files (*.xml)|*.xml", "Save Test Result");
                if (!string.IsNullOrEmpty(path))
                {
                    dM.SaveResultXml(passNum, failNum, totalNum, yeild, path);
                    ShowInfoMsg("成功儲存測試結果！");
                }
            }
            else
            {
                ShowWarnMsg("沒有權限執行此動作！");
            }
        }
        #endregion

        #region IO Button
        /// <summary>
        /// out1状态改变,触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbOut01_CheckedChanged(object sender, EventArgs e) {
            
        }


        #endregion
    }
}